a53bac4711
and all related things along with it, including a compatibility layer
3278 lines
142 KiB
C++
3278 lines
142 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Music Composition & Notation
|
|
//
|
|
// Copyright (C) 2011 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 "score.h"
|
|
#include "slur.h"
|
|
#include "staff.h"
|
|
#include "excerpt.h"
|
|
#include "chord.h"
|
|
#include "rest.h"
|
|
#include "keysig.h"
|
|
#include "volta.h"
|
|
#include "measure.h"
|
|
#include "beam.h"
|
|
#include "segment.h"
|
|
#include "ottava.h"
|
|
#include "stafftype.h"
|
|
#include "text.h"
|
|
#include "measurenumber.h"
|
|
#include "part.h"
|
|
#include "sig.h"
|
|
#include "box.h"
|
|
#include "dynamic.h"
|
|
#include "drumset.h"
|
|
#include "style.h"
|
|
#include "sym.h"
|
|
#include "xml.h"
|
|
#include "stringdata.h"
|
|
#include "tempo.h"
|
|
#include "tempotext.h"
|
|
#include "clef.h"
|
|
#include "barline.h"
|
|
#include "timesig.h"
|
|
#include "tuplet.h"
|
|
#include "spacer.h"
|
|
#include "stafftext.h"
|
|
#include "repeat.h"
|
|
#include "breath.h"
|
|
#include "tremolo.h"
|
|
#include "utils.h"
|
|
#include "accidental.h"
|
|
#include "fingering.h"
|
|
#include "marker.h"
|
|
#include "read206.h"
|
|
#include "bracketItem.h"
|
|
#include "harmony.h"
|
|
#include "lyrics.h"
|
|
#include "image.h"
|
|
#include "textframe.h"
|
|
#include "jump.h"
|
|
#include "textline.h"
|
|
#include "pedal.h"
|
|
|
|
namespace Ms {
|
|
|
|
static int g_guitarStrings[] = {40,45,50,55,59,64};
|
|
static int g_bassStrings[] = {28,33,38,43};
|
|
static int g_violinStrings[] = {55,62,69,76};
|
|
static int g_violaStrings[] = {48,55,62,69};
|
|
static int g_celloStrings[] = {36,43,50,57};
|
|
|
|
//---------------------------------------------------------
|
|
// StyleVal114
|
|
//---------------------------------------------------------
|
|
|
|
struct StyleVal2 {
|
|
Sid sid;
|
|
QVariant val;
|
|
};
|
|
|
|
static const StyleVal2 style114[] = {
|
|
// { Sid::lyricsMinBottomDistance, Spatium(2) },
|
|
{ Sid::lyricsDashForce, QVariant(false) },
|
|
{ Sid::frameSystemDistance, Spatium(1.0) },
|
|
{ Sid::minMeasureWidth, Spatium(4.0) },
|
|
// { Sid::endBarDistance, Spatium(0.30) },
|
|
|
|
{ Sid::repeatBarTips, QVariant(false) },
|
|
{ Sid::startBarlineSingle, QVariant(false) },
|
|
{ Sid::startBarlineMultiple, QVariant(true) },
|
|
{ Sid::bracketWidth, QVariant(0.35) },
|
|
{ Sid::bracketDistance, QVariant(0.25) },
|
|
{ Sid::clefLeftMargin, QVariant(0.5) },
|
|
{ Sid::keysigLeftMargin, QVariant(0.5) },
|
|
{ Sid::timesigLeftMargin, QVariant(0.5) },
|
|
{ Sid::clefKeyRightMargin, QVariant(1.75) },
|
|
{ Sid::clefBarlineDistance, QVariant(0.18) },
|
|
{ Sid::stemWidth, QVariant(0.13) },
|
|
{ Sid::shortenStem, QVariant(true) },
|
|
{ Sid::shortStemProgression, QVariant(0.25) },
|
|
{ Sid::shortestStem, QVariant(2.25) },
|
|
{ Sid::beginRepeatLeftMargin, QVariant(1.0) },
|
|
{ Sid::minNoteDistance, QVariant(0.4) },
|
|
{ Sid::barNoteDistance, QVariant(1.2) },
|
|
{ Sid::noteBarDistance, QVariant(1.0) },
|
|
{ Sid::measureSpacing, QVariant(1.2) },
|
|
{ Sid::staffLineWidth, QVariant(0.08) },
|
|
{ Sid::ledgerLineWidth, QVariant(0.12) },
|
|
{ Sid::akkoladeWidth, QVariant(1.6) },
|
|
{ Sid::accidentalDistance, QVariant(0.22) },
|
|
{ Sid::accidentalNoteDistance, QVariant(0.22) },
|
|
{ Sid::beamWidth, QVariant(0.48) },
|
|
{ Sid::beamDistance, QVariant(0.5) },
|
|
{ Sid::beamMinLen, QVariant(1.25) },
|
|
{ Sid::dotNoteDistance, QVariant(0.35) },
|
|
{ Sid::dotRestDistance, QVariant(0.25) },
|
|
{ Sid::dotDotDistance, QVariant(0.5) },
|
|
{ Sid::propertyDistanceHead, QVariant(1.0) },
|
|
{ Sid::propertyDistanceStem, QVariant(0.5) },
|
|
{ Sid::propertyDistance, QVariant(1.0) },
|
|
{ Sid::articulationMag, QVariant(qreal(1.0)) },
|
|
{ Sid::lastSystemFillLimit, QVariant(0.3) },
|
|
{ Sid::hairpinHeight, QVariant(1.2) },
|
|
{ Sid::hairpinContHeight, QVariant(0.5) },
|
|
{ Sid::hairpinLineWidth, QVariant(0.13) },
|
|
{ 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(qreal(0.7)) },
|
|
{ Sid::graceNoteMag, QVariant(qreal(0.7)) },
|
|
{ Sid::smallStaffMag, QVariant(qreal(0.7)) },
|
|
{ Sid::smallClefMag, QVariant(qreal(0.8)) },
|
|
{ Sid::genClef, QVariant(true) },
|
|
{ Sid::genKeysig, QVariant(true) },
|
|
{ Sid::genCourtesyTimesig, QVariant(true) },
|
|
{ Sid::genCourtesyKeysig, QVariant(true) },
|
|
{ Sid::useStandardNoteNames, QVariant(true) },
|
|
{ Sid::useGermanNoteNames, QVariant(false) },
|
|
{ Sid::useFullGermanNoteNames, QVariant(false) },
|
|
{ Sid::useSolfeggioNoteNames, QVariant(false) },
|
|
{ Sid::useFrenchNoteNames, QVariant(false) },
|
|
{ Sid::chordDescriptionFile, QVariant(QString("stdchords.xml")) },
|
|
{ Sid::chordStyle, QVariant(QString("custom")) },
|
|
{ Sid::chordsXmlFile, QVariant(true) },
|
|
// { Sid::harmonyY, QVariant(0.0) },
|
|
{ Sid::concertPitch, QVariant(false) },
|
|
{ Sid::createMultiMeasureRests, QVariant(false) },
|
|
{ Sid::minEmptyMeasures, QVariant(2) },
|
|
{ Sid::minMMRestWidth, QVariant(4.0) },
|
|
{ Sid::hideEmptyStaves, QVariant(false) },
|
|
{ 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::keySigNaturals, QVariant(int(KeySigNatural::BEFORE)) },
|
|
{ Sid::tupletMaxSlope, QVariant(qreal(0.5)) },
|
|
{ Sid::tupletOufOfStaff, QVariant(false) },
|
|
{ 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::hideInstrumentNameIfOneInstrument, QVariant(false) },
|
|
};
|
|
|
|
#define MM(x) ((x)/INCH)
|
|
|
|
//---------------------------------------------------------
|
|
// PaperSize
|
|
//---------------------------------------------------------
|
|
|
|
struct PaperSize {
|
|
const char* name;
|
|
qreal w, h; // size in inch
|
|
PaperSize(const char* n, qreal wi, qreal hi)
|
|
: name(n), w(wi), h(hi) {}
|
|
};
|
|
|
|
static const PaperSize paperSizes114[] = {
|
|
PaperSize("Custom", MM(1), MM(1)),
|
|
PaperSize("A4", MM(210), MM(297)),
|
|
PaperSize("B5", MM(176), MM(250)),
|
|
PaperSize("Letter", 8.5, 11),
|
|
PaperSize("Legal", 8.5, 14),
|
|
PaperSize("Executive", 7.5, 10),
|
|
PaperSize("A0", MM(841), MM(1189)),
|
|
PaperSize("A1", MM(594), MM(841)),
|
|
PaperSize("A2", MM(420), MM(594)),
|
|
PaperSize("A3", MM(297), MM(420)),
|
|
PaperSize("A5", MM(148), MM(210)),
|
|
PaperSize("A6", MM(105), MM(148)),
|
|
PaperSize("A7", MM(74), MM(105)),
|
|
PaperSize("A8", MM(52), MM(74)),
|
|
PaperSize("A9", MM(37), MM(52)),
|
|
PaperSize("A10", MM(26), MM(37)),
|
|
PaperSize("B0", MM(1000), MM(1414)),
|
|
PaperSize("B1", MM(707), MM(1000)),
|
|
PaperSize("B2", MM(500), MM(707)),
|
|
PaperSize("B3", MM(353), MM(500)),
|
|
PaperSize("B4", MM(250), MM(353)),
|
|
PaperSize("B6", MM(125), MM(176)),
|
|
PaperSize("B7", MM(88), MM(125)),
|
|
PaperSize("B8", MM(62), MM(88)),
|
|
PaperSize("B9", MM(44), MM(62)),
|
|
PaperSize("B10", MM(31), MM(44)),
|
|
PaperSize("Comm10E", MM(105), MM(241)),
|
|
PaperSize("DLE", MM(110), MM(220)),
|
|
PaperSize("Folio", MM(210), MM(330)),
|
|
PaperSize("Ledger", MM(432), MM(279)),
|
|
PaperSize("Tabloid", MM(279), MM(432)),
|
|
PaperSize(0, MM(1), MM(1)) // mark end of list
|
|
};
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// getPaperSize
|
|
//---------------------------------------------------------
|
|
|
|
static const PaperSize* getPaperSize114(const QString& name)
|
|
{
|
|
for (int i = 0; paperSizes114[i].name; ++i) {
|
|
if (name == paperSizes114[i].name)
|
|
return &paperSizes114[i];
|
|
}
|
|
qDebug("unknown paper size");
|
|
return &paperSizes114[0];
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// convertFromHtml
|
|
//---------------------------------------------------------
|
|
|
|
QString convertFromHtml(TextBase* t, const QString& ss)
|
|
{
|
|
QTextDocument doc;
|
|
doc.setHtml(ss.trimmed());
|
|
|
|
QString s;
|
|
qreal size = t->size(); // textStyle().size();
|
|
QString family = t->family(); // textStyle().family();
|
|
|
|
for (auto b = doc.firstBlock(); b.isValid() ; b = b.next()) {
|
|
if (!s.isEmpty())
|
|
s += "\n";
|
|
for (auto it = b.begin(); !it.atEnd(); ++it) {
|
|
QTextFragment f = it.fragment();
|
|
if (f.isValid()) {
|
|
QTextCharFormat tf = f.charFormat();
|
|
QFont font = tf.font();
|
|
qreal htmlSize = font.pointSizeF();
|
|
// html font sizes may have spatium adjustments; need to undo this
|
|
if (t->sizeIsSpatiumDependent())
|
|
htmlSize *= SPATIUM20 / t->spatium();
|
|
if (fabs(size - htmlSize) > 0.1) {
|
|
size = htmlSize;
|
|
s += QString("<font size=\"%1\"/>").arg(size);
|
|
}
|
|
if (family != font.family()) {
|
|
family = font.family();
|
|
s += QString("<font face=\"%1\"/>").arg(family);
|
|
}
|
|
if (font.bold())
|
|
s += "<b>";
|
|
if (font.italic())
|
|
s += "<i>";
|
|
if (font.underline())
|
|
s += "<u>";
|
|
s += f.text().toHtmlEscaped();
|
|
if (font.underline())
|
|
s += "</u>";
|
|
if (font.italic())
|
|
s += "</i>";
|
|
if (font.bold())
|
|
s += "</b>";
|
|
}
|
|
}
|
|
}
|
|
|
|
s.replace(QChar(0xe10e), QString("<sym>accidentalNatural</sym>")); //natural
|
|
s.replace(QChar(0xe10c), QString("<sym>accidentalSharp</sym>")); // sharp
|
|
s.replace(QChar(0xe10d), QString("<sym>accidentalFlat</sym>")); // flat
|
|
s.replace(QChar(0xe104), QString("<sym>metNoteHalfUp</sym>")), // note2_Sym
|
|
s.replace(QChar(0xe105), QString("<sym>metNoteQuarterUp</sym>")); // note4_Sym
|
|
s.replace(QChar(0xe106), QString("<sym>metNote8thUp</sym>")); // note8_Sym
|
|
s.replace(QChar(0xe107), QString("<sym>metNote16thUp</sym>")); // note16_Sym
|
|
s.replace(QChar(0xe108), QString("<sym>metNote32ndUp</sym>")); // note32_Sym
|
|
s.replace(QChar(0xe109), QString("<sym>metNote64thUp</sym>")); // note64_Sym
|
|
s.replace(QChar(0xe10a), QString("<sym>metAugmentationDot</sym>")); // dot
|
|
s.replace(QChar(0xe10b), QString("<sym>metAugmentationDot</sym><sym>space</sym><sym>metAugmentationDot</sym>")); // dotdot
|
|
s.replace(QChar(0xe167), QString("<sym>segno</sym>")); // segno
|
|
s.replace(QChar(0xe168), QString("<sym>coda</sym>")); // coda
|
|
s.replace(QChar(0xe169), QString("<sym>codaSquare</sym>")); // varcoda
|
|
return s;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// readTextProperties
|
|
//---------------------------------------------------------
|
|
|
|
static bool readTextProperties(XmlReader& e, TextBase* t, Element*)
|
|
{
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "style") {
|
|
int i = e.readInt();
|
|
Tid ss = Tid::DEFAULT;
|
|
switch (i) {
|
|
case 2: ss = Tid::TITLE; break;
|
|
case 3: ss = Tid::SUBTITLE; break;
|
|
case 4: ss = Tid::COMPOSER; break;
|
|
case 5: ss = Tid::POET; break;
|
|
|
|
case 6: ss = Tid::LYRICS_ODD; break;
|
|
case 7: ss = Tid::LYRICS_EVEN; break;
|
|
|
|
case 8: ss = Tid::FINGERING; break;
|
|
case 9: ss = Tid::INSTRUMENT_LONG; break;
|
|
case 10: ss = Tid::INSTRUMENT_SHORT; break;
|
|
case 11: ss = Tid::INSTRUMENT_EXCERPT; break;
|
|
|
|
case 12: ss = Tid::DYNAMICS; break;
|
|
case 13: ss = Tid::EXPRESSION; break;
|
|
case 14: ss = Tid::TEMPO; break;
|
|
case 15: ss = Tid::METRONOME; break;
|
|
case 16: ss = Tid::FOOTER; break; // TextStyleType::COPYRIGHT
|
|
case 17: ss = Tid::MEASURE_NUMBER; break;
|
|
case 18: ss = Tid::FOOTER; break; // TextStyleType::PAGE_NUMBER_ODD
|
|
case 19: ss = Tid::FOOTER; break; // TextStyleType::PAGE_NUMBER_EVEN
|
|
case 20: ss = Tid::TRANSLATOR; break;
|
|
case 21: ss = Tid::TUPLET; break;
|
|
|
|
case 22: ss = Tid::SYSTEM; break;
|
|
case 23: ss = Tid::STAFF; break;
|
|
case 24: ss = Tid::HARMONY_A; break;
|
|
case 25: ss = Tid::REHEARSAL_MARK; break;
|
|
case 26: ss = Tid::REPEAT_LEFT; break;
|
|
case 27: ss = Tid::VOLTA; break;
|
|
case 28: ss = Tid::FRAME; break;
|
|
case 29: ss = Tid::TEXTLINE; break;
|
|
case 30: ss = Tid::GLISSANDO; break;
|
|
case 31: ss = Tid::STRING_NUMBER; break;
|
|
|
|
case 32: ss = Tid::OTTAVA; break;
|
|
//?? case 33: ss = Tid::BENCH; break;
|
|
case 34: ss = Tid::HEADER; break;
|
|
case 35: ss = Tid::FOOTER; break;
|
|
case 0:
|
|
default:
|
|
qDebug("style %d invalid", i);
|
|
ss = Tid::DEFAULT;
|
|
break;
|
|
}
|
|
t->initTid(ss);
|
|
}
|
|
else if (tag == "subtype")
|
|
e.skipCurrentElement();
|
|
else if (tag == "html-data") {
|
|
QString ss = e.readXml();
|
|
QString s = convertFromHtml(t, ss);
|
|
// qDebug("html-data <%s>", qPrintable(s));
|
|
t->setXmlText(s);
|
|
}
|
|
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 == "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("readText: 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 == "bottom")
|
|
align = align | Align::BOTTOM;
|
|
else if (val == "top")
|
|
;
|
|
else if (val == "vcenter")
|
|
align = align | Align::VCENTER;
|
|
else if (val == "baseline")
|
|
align = align | Align::BASELINE;
|
|
else
|
|
qDebug("readText: unknown alignment: <%s>", qPrintable(val));
|
|
t->setAlign(align);
|
|
}
|
|
else if (tag == "rxoffset") { // TODO
|
|
e.readElementText();
|
|
}
|
|
else if (tag == "ryoffset") { // TODO
|
|
e.readElementText();
|
|
}
|
|
else if (tag == "yoffset") { // TODO
|
|
e.readElementText();
|
|
}
|
|
else if (tag == "systemFlag") { // TODO
|
|
e.readElementText();
|
|
}
|
|
else if (!t->readProperties(e))
|
|
return false;
|
|
t->setOffset(QPointF()); // ignore user offsets
|
|
t->setAutoplace(true);
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readText114
|
|
//---------------------------------------------------------
|
|
|
|
static void readText114(XmlReader& e, TextBase* t, Element* be)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
if (!readTextProperties(e, t, be))
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readAccidental
|
|
//---------------------------------------------------------
|
|
|
|
static void readAccidental(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());
|
|
bool isInt;
|
|
int i = text.toInt(&isInt);
|
|
if (isInt) {
|
|
a->setBracket(i & 0x8000 ? AccidentalBracket::PARENTHESIS : AccidentalBracket::BRACKET);
|
|
i &= ~0x8000;
|
|
AccidentalType at;
|
|
switch (i) {
|
|
case 0:
|
|
at = AccidentalType::NONE;
|
|
break;
|
|
case 6:
|
|
a->setBracket(AccidentalBracket::PARENTHESIS);
|
|
// fall through
|
|
case 1:
|
|
case 11:
|
|
at = AccidentalType::SHARP;
|
|
break;
|
|
case 7:
|
|
a->setBracket(AccidentalBracket::PARENTHESIS);
|
|
// fall through
|
|
case 2:
|
|
case 12:
|
|
at = AccidentalType::FLAT;
|
|
break;
|
|
case 8:
|
|
a->setBracket(AccidentalBracket::PARENTHESIS);
|
|
// fall through
|
|
case 3:
|
|
case 13:
|
|
at = AccidentalType::SHARP2;
|
|
break;
|
|
case 9:
|
|
a->setBracket(AccidentalBracket::PARENTHESIS);
|
|
// fall through
|
|
case 4:
|
|
case 14:
|
|
at = AccidentalType::FLAT2;
|
|
break;
|
|
case 10:
|
|
a->setBracket(AccidentalBracket::PARENTHESIS);
|
|
// fall through
|
|
case 5:
|
|
case 15:
|
|
at = AccidentalType::NATURAL;
|
|
break;
|
|
case 16:
|
|
at = AccidentalType::FLAT_SLASH;
|
|
break;
|
|
case 17:
|
|
at = AccidentalType::FLAT_SLASH2;
|
|
break;
|
|
case 18:
|
|
at = AccidentalType::MIRRORED_FLAT2;
|
|
break;
|
|
case 19:
|
|
at = AccidentalType::MIRRORED_FLAT;
|
|
break;
|
|
case 20:
|
|
at = AccidentalType::NONE;//AccidentalType::MIRRORED_FLAT_SLASH;
|
|
break;
|
|
case 21:
|
|
at = AccidentalType::NONE;//AccidentalType::FLAT_FLAT_SLASH;
|
|
break;
|
|
case 22:
|
|
at = AccidentalType::SHARP_SLASH;
|
|
break;
|
|
case 23:
|
|
at = AccidentalType::SHARP_SLASH2;
|
|
break;
|
|
case 24:
|
|
at = AccidentalType::SHARP_SLASH3;
|
|
break;
|
|
case 25:
|
|
at = AccidentalType::SHARP_SLASH4;
|
|
break;
|
|
case 26:
|
|
at = AccidentalType::SHARP_ARROW_UP;
|
|
break;
|
|
case 27:
|
|
at = AccidentalType::SHARP_ARROW_DOWN;
|
|
break;
|
|
case 28:
|
|
at = AccidentalType::NONE;//AccidentalType::SHARP_ARROW_BOTH;
|
|
break;
|
|
case 29:
|
|
at = AccidentalType::FLAT_ARROW_UP;
|
|
break;
|
|
case 30:
|
|
at = AccidentalType::FLAT_ARROW_DOWN;
|
|
break;
|
|
case 31:
|
|
at = AccidentalType::NONE;//AccidentalType::FLAT_ARROW_BOTH;
|
|
break;
|
|
case 32:
|
|
at = AccidentalType::NATURAL_ARROW_UP;
|
|
break;
|
|
case 33:
|
|
at = AccidentalType::NATURAL_ARROW_DOWN;
|
|
break;
|
|
case 34:
|
|
at = AccidentalType::NONE;//AccidentalType::NATURAL_ARROW_BOTH;
|
|
break;
|
|
default:
|
|
at = AccidentalType::NONE;
|
|
break;
|
|
}
|
|
a->setAccidentalType(at);
|
|
}
|
|
else {
|
|
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 (tag == "offset")
|
|
e.skipCurrentElement(); // ignore manual layout in older scores
|
|
else if (a->Element::readProperties(e))
|
|
;
|
|
else
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// convertHeadGroup
|
|
//---------------------------------------------------------
|
|
|
|
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;
|
|
default:
|
|
val = NoteHead::Group::HEAD_NORMAL;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// readFingering114
|
|
//---------------------------------------------------------
|
|
|
|
static void readFingering114(XmlReader& e, Fingering* fing)
|
|
{
|
|
bool isStringNumber = false;
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
|
|
if (tag == "html-data") {
|
|
auto htmlDdata = QTextDocumentFragment::fromHtml(e.readXml()).toPlainText();
|
|
htmlDdata.replace(" ", "");
|
|
fing->setPlainText(htmlDdata);
|
|
}
|
|
else if (tag == "subtype") {
|
|
auto subtype = e.readElementText();
|
|
if (subtype == "StringNumber") {
|
|
isStringNumber = true;
|
|
fing->setProperty(Pid::SUB_STYLE, QVariant(10));
|
|
fing->setPropertyFlags(Pid::SUB_STYLE, PropertyFlags::UNSTYLED);
|
|
}
|
|
}
|
|
else if (tag == "frame") {
|
|
auto frame = e.readInt();
|
|
if (frame)
|
|
if (isStringNumber) //default value is circle for stringnumber, square is setted in tag circle
|
|
fing->setFrameType(FrameType::CIRCLE);
|
|
else //default value is square for stringnumber, circle is setted in tag circle
|
|
fing->setFrameType(FrameType::SQUARE);
|
|
else
|
|
fing->setFrameType(FrameType::NO_FRAME);
|
|
}
|
|
else if (tag == "circle") {
|
|
auto circle = e.readInt();
|
|
if (circle)
|
|
fing->setFrameType(FrameType::CIRCLE);
|
|
else
|
|
fing->setFrameType(FrameType::SQUARE);
|
|
}
|
|
else {
|
|
e.skipCurrentElement();
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readNote
|
|
//---------------------------------------------------------
|
|
|
|
static void readNote(Note* note, XmlReader& e)
|
|
{
|
|
e.hasAccidental = false; // used for userAccidental backward compatibility
|
|
|
|
note->setTpc1(Tpc::TPC_INVALID);
|
|
note->setTpc2(Tpc::TPC_INVALID);
|
|
|
|
if (e.hasAttribute("pitch")) // obsolete
|
|
note->setPitch(e.intAttribute("pitch"));
|
|
if (e.hasAttribute("tpc")) // obsolete
|
|
note->setTpc1(e.intAttribute("tpc"));
|
|
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "Accidental") {
|
|
// on older scores, a note could have both a <userAccidental> tag and an <Accidental> tag
|
|
// if a userAccidental has some other property set (like for instance offset)
|
|
Accidental* a;
|
|
if (e.hasAccidental) // if the other tag has already been read,
|
|
a = note->accidental(); // re-use the accidental it constructed
|
|
else
|
|
a = new Accidental(note->score());
|
|
// the accidental needs to know the properties of the
|
|
// track it belongs to (??)
|
|
a->setTrack(note->track());
|
|
readAccidental(a, e);
|
|
if (!e.hasAccidental) // only the new accidental, if it has been added previously
|
|
note->add(a);
|
|
e.hasAccidental = true; // we now have an accidental
|
|
}
|
|
else if (tag == "Text") {
|
|
Fingering* f = new Fingering(note->score());
|
|
readFingering114(e, f);
|
|
note->add(f);
|
|
}
|
|
else if (tag == "onTimeType") {
|
|
if (e.readElementText() == "offset")
|
|
note->setOnTimeType(2);
|
|
else
|
|
note->setOnTimeType(1);
|
|
}
|
|
else if (tag == "offTimeType") {
|
|
if (e.readElementText() == "offset")
|
|
note->setOffTimeType(2);
|
|
else
|
|
note->setOffTimeType(1);
|
|
}
|
|
else if (tag == "onTimeOffset") {
|
|
if (note->onTimeType() == 1)
|
|
note->setOnTimeOffset(e.readInt() * 1000 / note->chord()->actualTicks().ticks());
|
|
else
|
|
note->setOnTimeOffset(e.readInt() * 10);
|
|
}
|
|
else if (tag == "offTimeOffset") {
|
|
if (note->offTimeType() == 1)
|
|
note->setOffTimeOffset(1000 + (e.readInt() * 1000 / note->chord()->actualTicks().ticks()));
|
|
else
|
|
note->setOffTimeOffset(1000 + (e.readInt() * 10));
|
|
}
|
|
else if (tag == "userAccidental") {
|
|
QString val(e.readElementText());
|
|
bool ok;
|
|
int k = val.toInt(&ok);
|
|
if (ok) {
|
|
// on older scores, a note could have both a <userAccidental> tag and an <Accidental> tag
|
|
// if a userAccidental has some other property set (like for instance offset)
|
|
// only construct a new accidental, if the other tag has not been read yet
|
|
// (<userAccidental> tag is only used in older scores: no need to check the score mscVersion)
|
|
if (!e.hasAccidental) {
|
|
Accidental* a = new Accidental(note->score());
|
|
note->add(a);
|
|
}
|
|
// TODO: for backward compatibility
|
|
bool bracket = k & 0x8000;
|
|
k &= 0xfff;
|
|
AccidentalType at = AccidentalType::NONE;
|
|
switch(k) {
|
|
case 0: at = AccidentalType::NONE; break;
|
|
case 1: at = AccidentalType::SHARP; break;
|
|
case 2: at = AccidentalType::FLAT; break;
|
|
case 3: at = AccidentalType::SHARP2; break;
|
|
case 4: at = AccidentalType::FLAT2; break;
|
|
case 5: at = AccidentalType::NATURAL; break;
|
|
|
|
case 6: at = AccidentalType::FLAT_SLASH; break;
|
|
case 7: at = AccidentalType::FLAT_SLASH2; break;
|
|
case 8: at = AccidentalType::MIRRORED_FLAT2; break;
|
|
case 9: at = AccidentalType::MIRRORED_FLAT; break;
|
|
case 10: at = AccidentalType::NONE; break; // AccidentalType::MIRRORED_FLAT_SLASH
|
|
case 11: at = AccidentalType::NONE; break; // AccidentalType::FLAT_FLAT_SLASH
|
|
|
|
case 12: at = AccidentalType::SHARP_SLASH; break;
|
|
case 13: at = AccidentalType::SHARP_SLASH2; break;
|
|
case 14: at = AccidentalType::SHARP_SLASH3; break;
|
|
case 15: at = AccidentalType::SHARP_SLASH4; break;
|
|
|
|
case 16: at = AccidentalType::SHARP_ARROW_UP; break;
|
|
case 17: at = AccidentalType::SHARP_ARROW_DOWN; break;
|
|
case 18: at = AccidentalType::NONE; break; // AccidentalType::SHARP_ARROW_BOTH
|
|
case 19: at = AccidentalType::FLAT_ARROW_UP; break;
|
|
case 20: at = AccidentalType::FLAT_ARROW_DOWN; break;
|
|
case 21: at = AccidentalType::NONE; break; // AccidentalType::FLAT_ARROW_BOTH
|
|
case 22: at = AccidentalType::NATURAL_ARROW_UP; break;
|
|
case 23: at = AccidentalType::NATURAL_ARROW_DOWN; break;
|
|
case 24: at = AccidentalType::NONE; break; // AccidentalType::NATURAL_ARROW_BOTH
|
|
case 25: at = AccidentalType::SORI; break;
|
|
case 26: at = AccidentalType::KORON; break;
|
|
}
|
|
note->accidental()->setAccidentalType(at);
|
|
|
|
note->accidental()->setBracket(AccidentalBracket(bracket));
|
|
|
|
note->accidental()->setRole(AccidentalRole::USER);
|
|
e.hasAccidental = true; // we now have an accidental
|
|
}
|
|
}
|
|
else if (tag == "offset")
|
|
e.skipCurrentElement(); // ignore manual layout in older scores
|
|
else if (tag == "move")
|
|
note->chord()->setStaffMove(e.readInt());
|
|
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;
|
|
switch (i) {
|
|
case 1:
|
|
val = NoteHead::Type::HEAD_WHOLE;
|
|
break;
|
|
case 2:
|
|
val = NoteHead::Type::HEAD_HALF;
|
|
break;
|
|
case 3:
|
|
val = NoteHead::Type::HEAD_QUARTER;
|
|
break;
|
|
case 4:
|
|
val = NoteHead::Type::HEAD_BREVIS;
|
|
break;
|
|
default:
|
|
val = NoteHead::Type::HEAD_AUTO;
|
|
}
|
|
note->setHeadType(val);
|
|
}
|
|
else if (readNoteProperties206(note, e))
|
|
;
|
|
else
|
|
e.unknown();
|
|
}
|
|
// ensure sane values:
|
|
note->setPitch(limit(note->pitch(), 0, 127));
|
|
|
|
if (note->concertPitch())
|
|
note->setTpc2(Tpc::TPC_INVALID);
|
|
else {
|
|
note->setPitch(note->pitch() + note->transposition());
|
|
note->setTpc2(note->tpc1());
|
|
note->setTpc1(Tpc::TPC_INVALID);
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
|
|
// 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 (!e.pasteMode() && !MScore::testMode) {
|
|
int tpc1Pitch = (tpc2pitch(note->tpc1()) + 12) % 12;
|
|
int tpc2Pitch = (tpc2pitch(note->tpc2()) + 12) % 12;
|
|
int concertPitch = note->pitch() % 12;
|
|
if (tpc1Pitch != concertPitch) {
|
|
qDebug("bad tpc1 - concertPitch = %d, tpc1 = %d", concertPitch, tpc1Pitch);
|
|
note->setPitch(note->pitch() + tpc1Pitch - concertPitch);
|
|
}
|
|
Interval v = note->staff()->part()->instrument(e.tick())->transpose();
|
|
int transposedPitch = (note->pitch() - v.chromatic) % 12;
|
|
if (tpc2Pitch != transposedPitch) {
|
|
qDebug("bad tpc2 - transposedPitch = %d, tpc2 = %d", transposedPitch, tpc2Pitch);
|
|
// just in case the staff transposition info is not reliable here,
|
|
v.flip();
|
|
note->setTpc2(Ms::transposeTpc(note->tpc1(), v, true));
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readClefType
|
|
//---------------------------------------------------------
|
|
|
|
static ClefType readClefType(const QString& s)
|
|
{
|
|
ClefType ct = ClefType::G;
|
|
bool ok;
|
|
int i = s.toInt(&ok);
|
|
if (ok) {
|
|
// convert obsolete old coding
|
|
switch (i) {
|
|
default:
|
|
case 0: ct = ClefType::G; break;
|
|
case 1: ct = ClefType::G8_VA; break;
|
|
case 2: ct = ClefType::G15_MA; break;
|
|
case 3: ct = ClefType::G8_VB; break;
|
|
case 4: ct = ClefType::F; break;
|
|
case 5: ct = ClefType::F8_VB; break;
|
|
case 6: ct = ClefType::F15_MB; break;
|
|
case 7: ct = ClefType::F_B; break;
|
|
case 8: ct = ClefType::F_C; break;
|
|
case 9: ct = ClefType::C1; break;
|
|
case 10: ct = ClefType::C2; break;
|
|
case 11: ct = ClefType::C3; break;
|
|
case 12: ct = ClefType::C4; break;
|
|
case 13: ct = ClefType::TAB; break;
|
|
case 14: ct = ClefType::PERC; break;
|
|
case 15: ct = ClefType::C5; break;
|
|
case 16: ct = ClefType::G_1; break;
|
|
case 17: ct = ClefType::F_8VA; break;
|
|
case 18: ct = ClefType::F_15MA; break;
|
|
case 19: ct = ClefType::PERC; break; // PERC2 no longer supported
|
|
case 20: ct = ClefType::TAB_SERIF; break;
|
|
}
|
|
}
|
|
return ct;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readClef
|
|
//---------------------------------------------------------
|
|
|
|
static void readClef(Clef* clef, XmlReader& e)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "subtype")
|
|
clef->setClefType(readClefType(e.readElementText()));
|
|
else if (!clef->readProperties(e))
|
|
e.unknown();
|
|
}
|
|
if (clef->clefType() == ClefType::INVALID)
|
|
clef->setClefType(ClefType::G);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readTuplet
|
|
//---------------------------------------------------------
|
|
|
|
static void readTuplet(Tuplet* tuplet, XmlReader& e)
|
|
{
|
|
int bl = -1;
|
|
tuplet->setId(e.intAttribute("id", 0));
|
|
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "subtype") // obsolete
|
|
e.skipCurrentElement();
|
|
else if (tag == "hasNumber") // obsolete even in 1.3
|
|
tuplet->setNumberType(e.readInt() ? TupletNumberType::SHOW_NUMBER : TupletNumberType::NO_TEXT);
|
|
else if (tag == "hasLine") { // obsolete even in 1.3
|
|
tuplet->setHasBracket(e.readInt());
|
|
tuplet->setBracketType(TupletBracketType::AUTO_BRACKET);
|
|
}
|
|
else if (tag == "baseLen") // obsolete even in 1.3
|
|
bl = e.readInt();
|
|
else if (tag == "tick")
|
|
tuplet->setTick(Fraction::fromTicks(e.readInt()));
|
|
else if (!readTupletProperties206(e, tuplet))
|
|
e.unknown();
|
|
}
|
|
Fraction r = (tuplet->ratio() == Fraction(1,1)) ? tuplet->ratio() : tuplet->ratio().reduced();
|
|
// this may be wrong, but at this stage it is kept for compatibility. It will be corrected afterwards
|
|
// during "sanitize" step
|
|
Fraction f(r.denominator(), tuplet->baseLen().fraction().denominator());
|
|
tuplet->setTicks(f.reduced());
|
|
if (bl != -1) { // obsolete, even in 1.3
|
|
TDuration d;
|
|
d.setVal(bl);
|
|
tuplet->setBaseLen(d);
|
|
d.setVal(bl * tuplet->ratio().denominator());
|
|
tuplet->setTicks(d.fraction());
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readTremolo
|
|
//---------------------------------------------------------
|
|
|
|
static void readTremolo(Tremolo* tremolo, XmlReader& e)
|
|
{
|
|
enum class OldTremoloType : char {
|
|
OLD_R8 = 0,
|
|
OLD_R16,
|
|
OLD_R32,
|
|
OLD_C8,
|
|
OLD_C16,
|
|
OLD_C32
|
|
};
|
|
while (e.readNextStartElement()) {
|
|
if (e.name() == "subtype") {
|
|
OldTremoloType sti = OldTremoloType(e.readElementText().toInt());
|
|
TremoloType st;
|
|
switch (sti) {
|
|
default:
|
|
case OldTremoloType::OLD_R8: st = TremoloType::R8; break;
|
|
case OldTremoloType::OLD_R16: st = TremoloType::R16; break;
|
|
case OldTremoloType::OLD_R32: st = TremoloType::R32; break;
|
|
case OldTremoloType::OLD_C8: st = TremoloType::C8; break;
|
|
case OldTremoloType::OLD_C16: st = TremoloType::C16; break;
|
|
case OldTremoloType::OLD_C32: st = TremoloType::C32; break;
|
|
}
|
|
tremolo->setTremoloType(st);
|
|
}
|
|
else if (!tremolo->Element::readProperties(e))
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readChord
|
|
//---------------------------------------------------------
|
|
|
|
static void readChord(Measure* m, 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 == "Attribute" || tag == "Articulation") {
|
|
Element* el = readArticulation(chord, e);
|
|
if (el->isFermata()) {
|
|
if (!chord->segment())
|
|
chord->setParent(m->getSegment(SegmentType::ChordRest, e.tick()));
|
|
chord->segment()->add(el);
|
|
}
|
|
else
|
|
chord->add(el);
|
|
}
|
|
else if (tag == "Tremolo") {
|
|
Tremolo* tremolo = new Tremolo(chord->score());
|
|
tremolo->setDurationType(chord->durationType());
|
|
chord->setTremolo(tremolo);
|
|
tremolo->setTrack(chord->track());
|
|
readTremolo(tremolo, e);
|
|
tremolo->setParent(chord);
|
|
}
|
|
else if (readChordProperties206(e, chord))
|
|
;
|
|
else
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readRest
|
|
//---------------------------------------------------------
|
|
|
|
static void readRest(Measure* m, Rest* rest, XmlReader& e)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "Attribute" || tag == "Articulation") {
|
|
Element* el = readArticulation(rest, e);
|
|
if (el->isFermata()) {
|
|
if (!rest->segment())
|
|
rest->setParent(m->getSegment(SegmentType::ChordRest, e.tick()));
|
|
rest->segment()->add(el);
|
|
}
|
|
else
|
|
rest->add(el);
|
|
}
|
|
else if (readChordRestProperties206(e, rest))
|
|
;
|
|
else
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readTempoText
|
|
//---------------------------------------------------------
|
|
|
|
void readTempoText(TempoText* t, XmlReader& e)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "tempo")
|
|
t->setTempo(e.readDouble());
|
|
else if (!readTextProperties(e, t, t))
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readStaffText
|
|
//---------------------------------------------------------
|
|
|
|
void readStaffText(StaffText* t, XmlReader& e)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
if (!readTextProperties(e, t, t))
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readLineSegment114
|
|
//---------------------------------------------------------
|
|
|
|
static void readLineSegment114(XmlReader& e, LineSegment* ls)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "off1")
|
|
ls->setOffset(e.readPoint() * ls->spatium());
|
|
else
|
|
ls->readProperties(e);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readTextLineProperties114
|
|
//---------------------------------------------------------
|
|
|
|
static bool readTextLineProperties114(XmlReader& e, TextLineBase* tl)
|
|
{
|
|
const QStringRef& tag(e.name());
|
|
|
|
if (tag == "beginText") {
|
|
Text* text = new Text(tl->score());
|
|
readText114(e, text, tl);
|
|
tl->setBeginText(text->xmlText());
|
|
tl->setPropertyFlags(Pid::BEGIN_TEXT, PropertyFlags::UNSTYLED);
|
|
delete text;
|
|
}
|
|
else if (tag == "continueText") {
|
|
Text* text = new Text(tl->score());
|
|
readText114(e, text, tl);
|
|
tl->setContinueText(text->xmlText());
|
|
tl->setPropertyFlags(Pid::CONTINUE_TEXT, PropertyFlags::UNSTYLED);
|
|
delete text;
|
|
}
|
|
else if (tag == "endText") {
|
|
Text* text = new Text(tl->score());
|
|
readText114(e, text, tl);
|
|
tl->setEndText(text->xmlText());
|
|
tl->setPropertyFlags(Pid::END_TEXT, PropertyFlags::UNSTYLED);
|
|
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 (tag == "Segment") {
|
|
LineSegment* ls = tl->createLineSegment();
|
|
ls->setTrack(tl->track()); // needed in read to get the right staff mag
|
|
readLineSegment114(e, ls);
|
|
// in v1.x "visible" is a property of the segment only;
|
|
// we must ensure that it propagates also to the parent element.
|
|
// That's why the visibility is set after adding the segment
|
|
// to the corresponding spanner
|
|
ls->setVisible(ls->visible());
|
|
ls->setOffset(QPointF()); // ignore offsets
|
|
ls->setAutoplace(true);
|
|
tl->add(ls);
|
|
}
|
|
else if (tl->readProperties(e))
|
|
return true;
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readVolta114
|
|
//---------------------------------------------------------
|
|
|
|
static void readVolta114(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 == "subtype") {
|
|
e.readInt(); // TODO
|
|
}
|
|
else if (tag == "lineWidth") {
|
|
volta->setLineWidth(e.readDouble() * volta->spatium());
|
|
volta->setPropertyFlags(Pid::LINE_WIDTH, PropertyFlags::UNSTYLED);
|
|
}
|
|
else if (!readTextLineProperties114(e, volta))
|
|
e.unknown();
|
|
}
|
|
volta->setOffset(QPointF()); // ignore offsets
|
|
volta->setAutoplace(true);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readOttava114
|
|
//---------------------------------------------------------
|
|
|
|
static void readOttava114(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 = int(OttavaType::OTTAVA_8VA);
|
|
for (unsigned i = 0; i < sizeof(ottavaDefault)/sizeof(*ottavaDefault); ++i) {
|
|
if (s == ottavaDefault[i].name) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//subtype are now in a different order...
|
|
if (idx == 1)
|
|
idx = 2;
|
|
else if (idx == 2)
|
|
idx = 1;
|
|
ottava->setOttavaType(OttavaType(idx));
|
|
}
|
|
else if (tag == "numbersOnly")
|
|
ottava->setNumbersOnly(e.readInt());
|
|
else if (tag == "lineWidth")
|
|
ottava->readProperty(e, Pid::LINE_WIDTH);
|
|
else if (tag == "lineStyle")
|
|
ottava->readProperty(e, Pid::LINE_STYLE);
|
|
else if (tag == "beginSymbol") { // obsolete
|
|
}
|
|
else if (tag == "continueSymbol") { // obsolete
|
|
}
|
|
else if (!readTextLineProperties114(e, ottava))
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// resolveSymCompatibility
|
|
//---------------------------------------------------------
|
|
|
|
static QString resolveSymCompatibility(SymId i, QString programVersion)
|
|
{
|
|
if (!programVersion.isEmpty() && programVersion < "1.1")
|
|
i = SymId(int(i) + 5);
|
|
switch (int(i)) {
|
|
case 197:
|
|
return "keyboardPedalPed";
|
|
case 191:
|
|
return "keyboardPedalUp";
|
|
case 193:
|
|
return "noSym"; //SymId(pedaldotSym);
|
|
case 192:
|
|
return "noSym"; //SymId(pedaldashSym);
|
|
case 139:
|
|
return "ornamentTrill";
|
|
default:
|
|
return "noSym";
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readTextLine114
|
|
//---------------------------------------------------------
|
|
|
|
static void readTextLine114(XmlReader& e, TextLine* textLine)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
|
|
if (tag == "lineVisible")
|
|
textLine->setLineVisible(e.readBool());
|
|
else if (tag == "beginHookHeight") {
|
|
textLine->setBeginHookHeight(Spatium(e.readDouble()));
|
|
}
|
|
else if (tag == "endHookHeight" || tag == "hookHeight") { // hookHeight is obsolete
|
|
textLine->setEndHookHeight(Spatium(e.readDouble()));
|
|
textLine->setPropertyFlags(Pid::END_HOOK_HEIGHT, PropertyFlags::UNSTYLED);
|
|
}
|
|
else if (tag == "hookUp") // obsolete
|
|
textLine->setEndHookHeight(Spatium(qreal(-1.0)));
|
|
else if (tag == "beginSymbol" || tag == "symbol") { // "symbol" is obsolete
|
|
QString text(e.readElementText());
|
|
textLine->setBeginText(QString("<sym>%1</sym>").arg(
|
|
text[0].isNumber()
|
|
? resolveSymCompatibility(SymId(text.toInt()), textLine->score()->mscoreVersion())
|
|
: text));
|
|
}
|
|
else if (tag == "continueSymbol") {
|
|
QString text(e.readElementText());
|
|
textLine->setContinueText(QString("<sym>%1</sym>").arg(
|
|
text[0].isNumber()
|
|
? resolveSymCompatibility(SymId(text.toInt()), textLine->score()->mscoreVersion())
|
|
: text));
|
|
}
|
|
else if (tag == "endSymbol") {
|
|
QString text(e.readElementText());
|
|
textLine->setEndText(QString("<sym>%1</sym>").arg(
|
|
text[0].isNumber()
|
|
? resolveSymCompatibility(SymId(text.toInt()), textLine->score()->mscoreVersion())
|
|
: text));
|
|
}
|
|
else if (tag == "beginSymbolOffset") // obsolete
|
|
e.readPoint();
|
|
else if (tag == "continueSymbolOffset") // obsolete
|
|
e.readPoint();
|
|
else if (tag == "endSymbolOffset") // obsolete
|
|
e.readPoint();
|
|
else if (tag == "beginTextPlace")
|
|
textLine->setBeginTextPlace(readPlacement(e));
|
|
else if (tag == "continueTextPlace")
|
|
textLine->setContinueTextPlace(readPlacement(e));
|
|
else if (tag == "endTextPlace")
|
|
textLine->setEndTextPlace(readPlacement(e));
|
|
else if (!readTextLineProperties114(e, textLine))
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readPedal114
|
|
//---------------------------------------------------------
|
|
|
|
static void readPedal114(XmlReader& e, Pedal* pedal)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "beginSymbol"
|
|
|| tag == "beginSymbolOffset"
|
|
|| tag == "endSymbol"
|
|
|| tag == "endSymbolOffset"
|
|
|| tag == "subtype"
|
|
)
|
|
e.skipCurrentElement();
|
|
else if (tag == "endHookHeight" || tag == "hookHeight") { // hookHeight is obsolete
|
|
pedal->setEndHookHeight(Spatium(e.readDouble()));
|
|
pedal->setPropertyFlags(Pid::END_HOOK_HEIGHT, PropertyFlags::UNSTYLED);
|
|
}
|
|
else if (tag == "lineWidth") {
|
|
pedal->setLineWidth(qreal(e.readDouble()));
|
|
pedal->setPropertyFlags(Pid::LINE_WIDTH, PropertyFlags::UNSTYLED);
|
|
}
|
|
else if (tag == "lineStyle") {
|
|
pedal->setLineStyle(Qt::PenStyle(e.readInt()));
|
|
pedal->setPropertyFlags(Pid::LINE_STYLE, PropertyFlags::UNSTYLED);
|
|
}
|
|
else if (!readTextLineProperties114(e, pedal))
|
|
e.unknown();
|
|
}
|
|
pedal->setBeginText("<sym>keyboardPedalPed</sym>");
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readHarmony114
|
|
//---------------------------------------------------------
|
|
|
|
static void readHarmony114(XmlReader& e, Harmony* h)
|
|
{
|
|
// convert table to tpc values
|
|
static const int table[] = {
|
|
14, 9, 16, 11, 18, 13, 8, 15, 10, 17, 12, 19
|
|
};
|
|
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "base") {
|
|
if (h->score()->mscVersion() >= 106)
|
|
h->setBaseTpc(e.readInt());
|
|
else
|
|
h->setBaseTpc(table[e.readInt()-1]);
|
|
}
|
|
else if (tag == "baseCase")
|
|
h->setBaseCase(static_cast<NoteCaseType>(e.readInt()));
|
|
else if (tag == "extension")
|
|
h->setId(e.readInt());
|
|
else if (tag == "name")
|
|
h->setTextName(e.readElementText());
|
|
else if (tag == "root") {
|
|
if (h->score()->mscVersion() >= 106)
|
|
h->setRootTpc(e.readInt());
|
|
else
|
|
h->setRootTpc(table[e.readInt()-1]);
|
|
}
|
|
else if (tag == "rootCase")
|
|
h->setRootCase(static_cast<NoteCaseType>(e.readInt()));
|
|
else if (tag == "degree") {
|
|
int degreeValue = 0;
|
|
int degreeAlter = 0;
|
|
QString degreeType = "";
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& t(e.name());
|
|
if (t == "degree-value")
|
|
degreeValue = e.readInt();
|
|
else if (t == "degree-alter")
|
|
degreeAlter = e.readInt();
|
|
else if (t == "degree-type")
|
|
degreeType = e.readElementText();
|
|
else
|
|
e.unknown();
|
|
}
|
|
if (degreeValue <= 0 || degreeValue > 13
|
|
|| degreeAlter < -2 || degreeAlter > 2
|
|
|| (degreeType != "add" && degreeType != "alter" && degreeType != "subtract")) {
|
|
qDebug("incorrect degree: degreeValue=%d degreeAlter=%d degreeType=%s",
|
|
degreeValue, degreeAlter, qPrintable(degreeType));
|
|
}
|
|
else {
|
|
if (degreeType == "add")
|
|
h->addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ADD));
|
|
else if (degreeType == "alter")
|
|
h->addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ALTER));
|
|
else if (degreeType == "subtract")
|
|
h->addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::SUBTRACT));
|
|
}
|
|
}
|
|
else if (tag == "leftParen") {
|
|
h->setLeftParen(true);
|
|
e.readNext();
|
|
}
|
|
else if (tag == "rightParen") {
|
|
h->setRightParen(true);
|
|
e.readNext();
|
|
}
|
|
else if (!readTextProperties(e, h, h))
|
|
e.unknown();
|
|
}
|
|
|
|
// TODO: now that we can render arbitrary chords,
|
|
// we could try to construct a full representation from a degree list.
|
|
// These will typically only exist for chords imported from MusicXML prior to MuseScore 2.0
|
|
// or constructed in the Chord Symbol Properties dialog.
|
|
|
|
if (h->rootTpc() != Tpc::TPC_INVALID) {
|
|
if (h->id() > 0) {
|
|
// positive id will happen only for scores that were created with explicit chord lists
|
|
// lookup id in chord list and generate new description if necessary
|
|
h->getDescription();
|
|
}
|
|
else
|
|
{
|
|
// default case: look up by name
|
|
// description will be found for any chord already read in this score
|
|
// and we will generate a new one if necessary
|
|
h->getDescription(h->hTextName());
|
|
}
|
|
}
|
|
else if (h->hTextName() == "") {
|
|
// unrecognized chords prior to 2.0 were stored as text with markup
|
|
// we need to strip away the markup
|
|
// this removes any user-applied formatting,
|
|
// but we no longer support user-applied formatting for chord symbols anyhow
|
|
// with any luck, the resulting text will be parseable now, so give it a shot
|
|
// h->createLayout();
|
|
QString s = h->plainText();
|
|
if (!s.isEmpty()) {
|
|
h->setHarmony(s);
|
|
return;
|
|
}
|
|
// empty text could also indicate a root-less slash chord ("/E")
|
|
// we'll fall through and render it normally
|
|
}
|
|
|
|
// render chord from description (or _textName)
|
|
h->render();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readMeasure
|
|
//---------------------------------------------------------
|
|
|
|
static void readMeasure(Measure* m, int staffIdx, XmlReader& e)
|
|
{
|
|
Segment* segment = 0;
|
|
qreal _spatium = m->spatium();
|
|
|
|
QList<Chord*> graceNotes;
|
|
|
|
//sort tuplet elements. needed for nested tuplets #22537
|
|
for (Tuplet* t : e.tuplets())
|
|
t->sortElements();
|
|
e.tuplets().clear();
|
|
e.setTrack(staffIdx * VOICES);
|
|
|
|
m->createStaves(staffIdx);
|
|
|
|
// tick is obsolete
|
|
if (e.hasAttribute("tick"))
|
|
e.setTick(Fraction::fromTicks(m->score()->fileDivision(e.intAttribute("tick"))));
|
|
|
|
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")));
|
|
m->score()->sigmap()->add(m->tick().ticks(), SigEvent(m->ticks(), m->timesig()));
|
|
m->score()->sigmap()->add(m->endTick().ticks(), SigEvent(m->timesig()));
|
|
}
|
|
|
|
Staff* staff = m->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(m->score()->fileDivision(e.readInt())));
|
|
lastTick = e.tick();
|
|
}
|
|
else if (tag == "BarLine") {
|
|
BarLine* barLine = new BarLine(m->score());
|
|
barLine->setTrack(e.track());
|
|
barLine->resetProperty(Pid::BARLINE_SPAN);
|
|
barLine->resetProperty(Pid::BARLINE_SPAN_FROM);
|
|
barLine->resetProperty(Pid::BARLINE_SPAN_TO);
|
|
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tg(e.name());
|
|
if (tg == "subtype") {
|
|
BarLineType t = BarLineType::NORMAL;
|
|
switch (e.readInt()) {
|
|
default:
|
|
case 0:
|
|
t = BarLineType::NORMAL;
|
|
break;
|
|
case 1:
|
|
t = BarLineType::DOUBLE;
|
|
break;
|
|
case 2:
|
|
t = BarLineType::START_REPEAT;
|
|
break;
|
|
case 3:
|
|
t = BarLineType::END_REPEAT;
|
|
break;
|
|
case 4:
|
|
t = BarLineType::BROKEN;
|
|
break;
|
|
case 5:
|
|
t = BarLineType::END;
|
|
break;
|
|
case 6:
|
|
t = BarLineType::END_START_REPEAT;
|
|
break;
|
|
}
|
|
barLine->setBarLineType(t);
|
|
}
|
|
else if (!barLine->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 (barLine->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(barLine);
|
|
}
|
|
else if (tag == "Chord") {
|
|
Chord* chord = new Chord(m->score());
|
|
chord->setTrack(e.track());
|
|
readChord(m, chord, e);
|
|
if (!chord->segment())
|
|
chord->setParent(m->getSegment(SegmentType::ChordRest, e.tick()));
|
|
segment = chord->segment();
|
|
if (chord->noteType() != NoteType::NORMAL) {
|
|
graceNotes.push_back(chord);
|
|
}
|
|
else {
|
|
segment->add(chord);
|
|
Q_ASSERT(segment->segmentType() == SegmentType::ChordRest);
|
|
|
|
for (int i = 0; i < graceNotes.size(); ++i) {
|
|
Chord* gc = graceNotes[i];
|
|
gc->setGraceIndex(i);
|
|
chord->add(gc);
|
|
}
|
|
graceNotes.clear();
|
|
Fraction crticks = chord->actualTicks();
|
|
|
|
if (chord->tremolo()) {
|
|
Tremolo* tremolo = chord->tremolo();
|
|
if (tremolo->twoNotes()) {
|
|
int track = chord->track();
|
|
Segment* ss = 0;
|
|
for (Segment* ps = m->first(SegmentType::ChordRest); ps; ps = ps->next(SegmentType::ChordRest)) {
|
|
if (ps->tick() >= e.tick())
|
|
break;
|
|
if (ps->element(track))
|
|
ss = ps;
|
|
}
|
|
Chord* pch = 0; // previous chord
|
|
if (ss) {
|
|
ChordRest* cr = toChordRest(ss->element(track));
|
|
if (cr && cr->type() == ElementType::CHORD)
|
|
pch = toChord(cr);
|
|
}
|
|
if (pch) {
|
|
tremolo->setParent(pch);
|
|
pch->setTremolo(tremolo);
|
|
chord->setTremolo(0);
|
|
// force duration to half
|
|
Fraction pts(timeStretch * pch->globalTicks());
|
|
pch->setTicks(pts * Fraction(1,2));
|
|
chord->setTicks(crticks * Fraction(1,2));
|
|
}
|
|
else {
|
|
qDebug("tremolo: first note not found");
|
|
}
|
|
crticks = crticks * Fraction(1,2);
|
|
}
|
|
else {
|
|
tremolo->setParent(chord);
|
|
}
|
|
}
|
|
lastTick = e.tick();
|
|
e.incTick(crticks);
|
|
}
|
|
}
|
|
else if (tag == "Rest") {
|
|
Rest* rest = new Rest(m->score());
|
|
rest->setDurationType(TDuration::DurationType::V_MEASURE);
|
|
rest->setTicks(m->timesig()/timeStretch);
|
|
rest->setTrack(e.track());
|
|
readRest(m, rest, e);
|
|
if (!rest->segment())
|
|
rest->setParent(m->getSegment(SegmentType::ChordRest, e.tick()));
|
|
segment = rest->segment();
|
|
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(m->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(m->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);
|
|
}
|
|
m->score()->addSpanner(sl);
|
|
}
|
|
else if (tag == "HairPin"
|
|
|| tag == "Pedal"
|
|
|| tag == "Ottava"
|
|
|| tag == "Trill"
|
|
|| tag == "TextLine"
|
|
|| tag == "Volta") {
|
|
Spanner* sp = toSpanner(Element::name2Element(tag, m->score()));
|
|
sp->setTrack(e.track());
|
|
sp->setTick(e.tick());
|
|
// ?? sp->setAnchor(Spanner::Anchor::SEGMENT);
|
|
if (tag == "Volta")
|
|
readVolta114(e, toVolta(sp));
|
|
else
|
|
readTextLine206(e, toTextLineBase(sp));
|
|
m->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(m->score());
|
|
rm->setTrack(e.track());
|
|
readRest(m, rm, e);
|
|
segment = m->getSegment(SegmentType::ChordRest, e.tick());
|
|
segment->add(rm);
|
|
if (rm->actualTicks().isZero()) // might happen with 1.3 scores
|
|
rm->setTicks(m->ticks());
|
|
lastTick = e.tick();
|
|
e.incTick(m->ticks());
|
|
}
|
|
else if (tag == "Clef") {
|
|
Clef* clef = new Clef(m->score());
|
|
clef->setTrack(e.track());
|
|
readClef(clef, e);
|
|
if (m->score()->mscVersion() < 113)
|
|
clef->setOffset(QPointF());
|
|
clef->setGenerated(false);
|
|
// MS3 doesn't support wrong clef for staff type: Default to G
|
|
bool isDrumStaff = staff->isDrumStaff(e.tick());
|
|
if (clef->clefType() == ClefType::TAB
|
|
|| (clef->clefType() == ClefType::PERC && !isDrumStaff)
|
|
|| (clef->clefType() != ClefType::PERC && isDrumStaff)) {
|
|
clef->setClefType(ClefType::G);
|
|
staff->clefList().erase(e.tick().ticks());
|
|
staff->clefList().insert(std::pair<int,ClefType>(e.tick().ticks(), ClefType::G));
|
|
}
|
|
|
|
// there may be more than one clef segment for same tick position
|
|
// the first clef may be missing and is added later in layout
|
|
|
|
bool header;
|
|
if (e.tick() != m->tick())
|
|
header = false;
|
|
else if (!segment)
|
|
header = true;
|
|
else {
|
|
header = true;
|
|
for (Segment* s = m->segments().first(); s && s->rtick().isZero(); s = s->next()) {
|
|
if (s->isKeySigType() || s->isTimeSigType()) {
|
|
// hack: there may be other segment types which should
|
|
// generate a clef at current position
|
|
header = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
segment = m->getSegment(header ? SegmentType::HeaderClef : SegmentType::Clef, e.tick());
|
|
segment->add(clef);
|
|
}
|
|
else if (tag == "TimeSig") {
|
|
TimeSig* ts = new TimeSig(m->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);
|
|
}
|
|
}
|
|
else if (tag == "KeySig") {
|
|
KeySig* ks = new KeySig(m->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());
|
|
}
|
|
}
|
|
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 == "Lyrics") {
|
|
Lyrics* l = new Lyrics(m->score());
|
|
l->setTrack(e.track());
|
|
|
|
int iEndTick = 0; // used for backward compatibility
|
|
Text* _verseNumber = 0;
|
|
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& t(e.name());
|
|
if (t == "no")
|
|
l->setNo(e.readInt());
|
|
else if (t == "syllabic") {
|
|
QString val(e.readElementText());
|
|
if (val == "single")
|
|
l->setSyllabic(Lyrics::Syllabic::SINGLE);
|
|
else if (val == "begin")
|
|
l->setSyllabic(Lyrics::Syllabic::BEGIN);
|
|
else if (val == "end")
|
|
l->setSyllabic(Lyrics::Syllabic::END);
|
|
else if (val == "middle")
|
|
l->setSyllabic(Lyrics::Syllabic::MIDDLE);
|
|
else
|
|
qDebug("bad syllabic property");
|
|
}
|
|
else if (t == "endTick") { // obsolete
|
|
// store <endTick> tag value until a <ticks> tag has been read
|
|
// which positions this lyrics element in the score
|
|
iEndTick = e.readInt();
|
|
}
|
|
else if (t == "ticks")
|
|
l->setTicks(Fraction::fromTicks(e.readInt()));
|
|
else if (t == "Number") { // obsolete
|
|
_verseNumber = new Text(l->score());
|
|
readText114(e, _verseNumber, l);
|
|
_verseNumber->setParent(l);
|
|
}
|
|
else if (!readTextProperties(e, l, l))
|
|
e.unknown();
|
|
}
|
|
// if any endTick, make it relative to current tick
|
|
if (iEndTick) {
|
|
l->setTicks(Fraction::fromTicks(iEndTick - e.tick().ticks()));
|
|
// qDebug("Lyrics::endTick: %d ticks %d", iEndTick, _ticks);
|
|
}
|
|
if (_verseNumber) {
|
|
// TODO: add text to main text
|
|
}
|
|
|
|
delete _verseNumber;
|
|
|
|
segment = m->getSegment(SegmentType::ChordRest, e.tick());
|
|
ChordRest* cr = toChordRest(segment->element(l->track()));
|
|
if (!cr)
|
|
cr = toChordRest(segment->element(e.track())); // in case lyric itself has bad track info
|
|
if (!cr)
|
|
qDebug("Internal error: no chord/rest for lyrics");
|
|
else
|
|
cr->add(l);
|
|
}
|
|
else if (tag == "Text") {
|
|
StaffText* t = new StaffText(m->score());
|
|
t->setTrack(e.track());
|
|
readStaffText(t, e);
|
|
if (t->empty()) {
|
|
qDebug("reading empty text: deleted");
|
|
delete t;
|
|
}
|
|
else {
|
|
segment = m->getSegment(SegmentType::ChordRest, e.tick());
|
|
segment->add(t);
|
|
}
|
|
}
|
|
else if (tag == "Dynamic") {
|
|
Dynamic* dyn = new Dynamic(m->score());
|
|
dyn->setTrack(e.track());
|
|
dyn->read(e);
|
|
dyn->setDynamicType(dyn->xmlText());
|
|
segment = m->getSegment(SegmentType::ChordRest, e.tick());
|
|
segment->add(dyn);
|
|
}
|
|
else if (tag == "Tempo") {
|
|
TempoText* t = new TempoText(m->score());
|
|
t->setTrack(e.track());
|
|
readTempoText(t, e);
|
|
segment = m->getSegment(SegmentType::ChordRest, e.tick());
|
|
segment->add(t);
|
|
}
|
|
else if (tag == "StaffText") {
|
|
StaffText* t = new StaffText(m->score());
|
|
t->setTrack(e.track());
|
|
readStaffText(t, e);
|
|
segment = m->getSegment(SegmentType::ChordRest, e.tick());
|
|
segment->add(t);
|
|
}
|
|
else if (tag == "Harmony") {
|
|
Harmony* h = new Harmony(m->score());
|
|
h->setTrack(e.track());
|
|
readHarmony114(e, h);
|
|
segment = m->getSegment(SegmentType::ChordRest, e.tick());
|
|
segment->add(h);
|
|
}
|
|
else if (tag == "Harmony"
|
|
|| tag == "FretDiagram"
|
|
|| tag == "TremoloBar"
|
|
|| tag == "Symbol"
|
|
|| tag == "StaffText"
|
|
|| tag == "RehearsalMark"
|
|
|| tag == "InstrumentChange"
|
|
|| tag == "StaffState"
|
|
) {
|
|
Element* el = Element::name2Element(tag, m->score());
|
|
// hack - needed because tick tags are unreliable in 1.3 scores
|
|
// for symbols attached to anything but a measure
|
|
if (el->type() == ElementType::SYMBOL)
|
|
el->setParent(m); // this will get reset when adding to segment
|
|
el->setTrack(e.track());
|
|
el->read(e);
|
|
segment = m->getSegment(SegmentType::ChordRest, e.tick());
|
|
segment->add(el);
|
|
}
|
|
else if (tag == "Jump") {
|
|
Jump* j = new Jump(m->score());
|
|
j->setTrack(e.track());
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& t(e.name());
|
|
if (t == "jumpTo")
|
|
j->setJumpTo(e.readElementText());
|
|
else if (t == "playUntil")
|
|
j->setPlayUntil(e.readElementText());
|
|
else if (t == "continueAt")
|
|
j->setContinueAt(e.readElementText());
|
|
else if (t == "playRepeats")
|
|
j->setPlayRepeats(e.readBool());
|
|
else if (t == "subtype")
|
|
e.readInt();
|
|
else if (!j->TextBase::readProperties(e))
|
|
e.unknown();
|
|
}
|
|
m->add(j);
|
|
}
|
|
else if (tag == "Marker") {
|
|
Marker* a = new Marker(m->score());
|
|
a->setTrack(e.track());
|
|
|
|
Marker::Type mt = Marker::Type::SEGNO;
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& t(e.name());
|
|
if (t == "subtype") {
|
|
QString s(e.readElementText());
|
|
a->setLabel(s);
|
|
mt = a->markerType(s);
|
|
}
|
|
else if (!a->TextBase::readProperties(e))
|
|
e.unknown();
|
|
}
|
|
a->setMarkerType(mt);
|
|
|
|
if (a->markerType() == Marker::Type::SEGNO || a->markerType() == Marker::Type::CODA ||
|
|
a->markerType() == Marker::Type::VARCODA || a->markerType() == Marker::Type::CODETTA) {
|
|
// force the marker type for correct display
|
|
a->setXmlText("");
|
|
a->setMarkerType(a->markerType());
|
|
//TODO::ws a->initElementStyle(Tid::REPEAT_LEFT);
|
|
}
|
|
m->add(a);
|
|
}
|
|
else if (tag == "Image") {
|
|
if (MScore::noImages)
|
|
e.skipCurrentElement();
|
|
else {
|
|
Element* el = Element::name2Element(tag, m->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(m->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(m->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(m->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(m->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(m->score());
|
|
beam->setTrack(e.track());
|
|
beam->read(e);
|
|
beam->setParent(0);
|
|
e.addBeam(beam);
|
|
}
|
|
else if (tag == "Segment") {
|
|
segment->read(e);
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& t(e.name());
|
|
if (t == "off1") {
|
|
qreal o = e.readDouble();
|
|
qDebug("TODO: off1 %f", o);
|
|
}
|
|
else
|
|
e.unknown();
|
|
}
|
|
}
|
|
else if (tag == "MeasureNumber") {
|
|
MeasureNumber* noText = new MeasureNumber(m->score());
|
|
readText114(e, noText, m);
|
|
noText->setTrack(e.track());
|
|
noText->setParent(m);
|
|
m->setNoText(noText->staffIdx(), noText);
|
|
}
|
|
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();
|
|
}
|
|
// For nested tuplets created with MuseScore 1.3 tuplet dialog (i.e. "Other..." dialog),
|
|
// the parent tuplet was not set. Try to infere if the tuplet was actually a nested tuplet
|
|
for (Tuplet* tuplet : e.tuplets()) {
|
|
Fraction tupletTick = tuplet->tick();
|
|
Fraction tupletDuration = tuplet->actualTicks() - Fraction::fromTicks(1);
|
|
std::vector<DurationElement*> tElements = tuplet->elements();
|
|
for (Tuplet* tuplet2 : e.tuplets()) {
|
|
if ((tuplet2->tuplet()) || (tuplet2->voice() != tuplet->voice())) // already a nested tuplet or in a different voice
|
|
continue;
|
|
// int possibleDuration = tuplet2->duration().ticks() * tuplet->ratio().denominator() / tuplet->ratio().numerator() - 1;
|
|
Fraction possibleDuration = tuplet2->ticks() * Fraction(tuplet->ratio().denominator(), (tuplet->ratio().numerator()-1));
|
|
|
|
if ((tuplet2 != tuplet) && (tuplet2->tick() >= tupletTick) && (tuplet2->tick() < tupletTick + tupletDuration) && (tuplet2->tick() + possibleDuration < tupletTick + tupletDuration)) {
|
|
bool found = false;
|
|
for (DurationElement* de : tElements) {
|
|
if (de == tuplet2)
|
|
found = true;
|
|
}
|
|
if (!found) {
|
|
qDebug("Adding tuplet %p as nested tuplet to tuplet %p",tuplet2,tuplet);
|
|
tuplet2->setTuplet(tuplet);
|
|
tuplet->add(tuplet2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
e.checkTuplets();
|
|
m->connectTremolo();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readBoxProperties
|
|
//---------------------------------------------------------
|
|
|
|
static void readBox(XmlReader& e, Box* b);
|
|
|
|
static bool readBoxProperties(XmlReader& e, Box* b)
|
|
{
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "height")
|
|
b->setBoxHeight(Spatium(e.readDouble()));
|
|
else if (tag == "width")
|
|
b->setBoxWidth(Spatium(e.readDouble()));
|
|
else if (tag == "topGap")
|
|
b->readProperty(e, Pid::TOP_GAP);
|
|
else if (tag == "bottomGap") {
|
|
b->readProperty(e, Pid::BOTTOM_GAP);
|
|
}
|
|
else if (tag == "leftMargin")
|
|
b->setLeftMargin(e.readDouble());
|
|
else if (tag == "rightMargin")
|
|
b->setRightMargin(e.readDouble());
|
|
else if (tag == "topMargin")
|
|
b->setTopMargin(e.readDouble());
|
|
else if (tag == "bottomMargin")
|
|
b->setBottomMargin(e.readDouble());
|
|
else if (tag == "offset")
|
|
b->setOffset(e.readPoint() * b->spatium());
|
|
else if (tag == "pos") // ignore
|
|
e.readPoint();
|
|
else if (tag == "Text") {
|
|
Text* t;
|
|
if (b->isTBox()) {
|
|
t = toTBox(b)->text();
|
|
readText114(e, t, t);
|
|
}
|
|
else {
|
|
t = new Text(b->score());
|
|
readText114(e, t, t);
|
|
if (t->empty())
|
|
qDebug("read empty text");
|
|
else
|
|
b->add(t);
|
|
}
|
|
}
|
|
else if (tag == "Symbol") {
|
|
Symbol* s = new Symbol(b->score());
|
|
s->read(e);
|
|
b->add(s);
|
|
}
|
|
else if (tag == "Image") {
|
|
if (MScore::noImages)
|
|
e.skipCurrentElement();
|
|
else {
|
|
Image* image = new Image(b->score());
|
|
image->setTrack(e.track());
|
|
image->read(e);
|
|
b->add(image);
|
|
}
|
|
}
|
|
else if (tag == "HBox") {
|
|
HBox* hb = new HBox(b->score());
|
|
readBox(e, hb);
|
|
b->add(hb);
|
|
}
|
|
else if (tag == "VBox") {
|
|
VBox* vb = new VBox(b->score());
|
|
readBox(e, vb);
|
|
b->add(vb);
|
|
}
|
|
// else if (MeasureBase::readProperties(e))
|
|
// ;
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readBox
|
|
//---------------------------------------------------------
|
|
|
|
static void readBox(XmlReader& e, Box* b)
|
|
{
|
|
b->setLeftMargin(0.0);
|
|
b->setRightMargin(0.0);
|
|
b->setTopMargin(0.0);
|
|
b->setBottomMargin(0.0);
|
|
b->setBoxHeight(Spatium(0)); // override default set in constructor
|
|
b->setBoxWidth(Spatium(0));
|
|
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "HBox") {
|
|
HBox* hb = new HBox(b->score());
|
|
readBox(e, hb);
|
|
b->add(hb);
|
|
}
|
|
else if (tag == "VBox") {
|
|
VBox* vb = new VBox(b->score());
|
|
readBox(e, vb);
|
|
b->add(vb);
|
|
}
|
|
else if (!readBoxProperties(e, b))
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readStaffContent
|
|
//---------------------------------------------------------
|
|
|
|
static void readStaffContent(Score* score, XmlReader& e)
|
|
{
|
|
int staff = e.intAttribute("id", 1) - 1;
|
|
e.setTick(Fraction(0,1));
|
|
e.setTrack(staff * VOICES);
|
|
|
|
Measure* measure = score->firstMeasure();
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
|
|
if (tag == "Measure") {
|
|
if (staff == 0) {
|
|
measure = new Measure(score);
|
|
measure->setTick(e.tick());
|
|
const SigEvent& ev = score->sigmap()->timesig(measure->tick());
|
|
measure->setTicks(ev.timesig());
|
|
measure->setTimesig(ev.nominal());
|
|
|
|
readMeasure(measure, staff, e);
|
|
measure->checkMeasure(staff);
|
|
|
|
if (!measure->isMMRest()) {
|
|
score->measures()->add(measure);
|
|
e.setLastMeasure(measure);
|
|
e.setTick(measure->tick() + measure->ticks());
|
|
}
|
|
else {
|
|
// this is a multi measure rest
|
|
// always preceded by the first measure it replaces
|
|
Measure* m = e.lastMeasure();
|
|
|
|
if (m) {
|
|
m->setMMRest(measure);
|
|
measure->setTick(m->tick());
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
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 == "HBox" || tag == "VBox" || tag == "TBox" || tag == "FBox") {
|
|
Box* mb = toBox(Element::name2Element(tag, score));
|
|
readBox(e, mb);
|
|
mb->setTick(e.tick());
|
|
score->measures()->add(mb);
|
|
}
|
|
else if (tag == "tick")
|
|
e.setTick(Fraction::fromTicks(score->fileDivision(e.readInt())));
|
|
else
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readStaff
|
|
//---------------------------------------------------------
|
|
|
|
static void readStaff(Staff* staff, XmlReader& e)
|
|
{
|
|
Score* _score = staff->score();
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "lines") {
|
|
int lines = e.readInt();
|
|
staff->setLines(Fraction(0,1), lines);
|
|
if (lines != 5) {
|
|
staff->setBarLineFrom(lines == 1 ? BARLINE_SPAN_1LINESTAFF_FROM : 0);
|
|
staff->setBarLineTo(lines == 1 ? BARLINE_SPAN_1LINESTAFF_TO : (lines - 1) * 2);
|
|
}
|
|
}
|
|
else if (tag == "small")
|
|
staff->setSmall(Fraction(0,1), e.readInt());
|
|
else if (tag == "invisible")
|
|
staff->setInvisible(e.readInt());
|
|
else if (tag == "slashStyle")
|
|
e.skipCurrentElement();
|
|
else if (tag == "cleflist") {
|
|
// QList<std::pair<int, ClefType>>& cl = e.clefs(idx());
|
|
staff->clefList().clear();
|
|
while (e.readNextStartElement()) {
|
|
if (e.name() == "clef") {
|
|
int tick = e.intAttribute("tick", 0);
|
|
ClefType ct = readClefType(e.attribute("idx", "0"));
|
|
staff->clefList().insert(std::pair<int,ClefType>(_score->fileDivision(tick), ct));
|
|
e.readNext();
|
|
}
|
|
else
|
|
e.unknown();
|
|
}
|
|
if (staff->clefList().empty())
|
|
staff->clefList().insert(std::pair<int,ClefType>(0, ClefType::G));
|
|
}
|
|
else if (tag == "keylist")
|
|
staff->keyList()->read(e, _score);
|
|
else if (tag == "bracket") {
|
|
int col = staff->brackets().size();
|
|
staff->setBracketType(col, BracketType(e.intAttribute("type", -1)));
|
|
staff->setBracketSpan(col, e.intAttribute("span", 0));
|
|
e.readNext();
|
|
}
|
|
else if (tag == "barLineSpan")
|
|
staff->setBarLineSpan(e.readInt());
|
|
else
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// 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 (ds->readProperties(e, pitch))
|
|
;
|
|
else
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readInstrument
|
|
//---------------------------------------------------------
|
|
|
|
static void readInstrument(Instrument *i, Part* p, XmlReader& e)
|
|
{
|
|
int program = -1;
|
|
int bank = 0;
|
|
int chorus = 30;
|
|
int reverb = 30;
|
|
int volume = 100;
|
|
int pan = 60;
|
|
bool customDrumset = false;
|
|
i->clearChannels(); // remove default channel
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "chorus")
|
|
chorus = e.readInt();
|
|
else if (tag == "reverb")
|
|
reverb = e.readInt();
|
|
else if (tag == "midiProgram")
|
|
program = e.readInt();
|
|
else if (tag == "volume")
|
|
volume = e.readInt();
|
|
else if (tag == "pan")
|
|
pan = e.readInt();
|
|
else if (tag == "midiChannel")
|
|
e.skipCurrentElement();
|
|
else 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();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Fix user bank controller read
|
|
for (Channel* c : i->channel()) {
|
|
if (c->bank() == 0)
|
|
c->setUserBankController(false);
|
|
}
|
|
|
|
// Read single-note dynamics from template
|
|
i->setSingleNoteDynamicsFromTemplate();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readPart
|
|
//---------------------------------------------------------
|
|
|
|
static void readPart(Part* part, XmlReader& e)
|
|
{
|
|
Score* _score = part->score();
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "Staff") {
|
|
Staff* staff = new Staff(_score);
|
|
staff->setPart(part);
|
|
staff->setStaffType(Fraction(0,1), StaffType()); // will reset later if needed
|
|
_score->staves().push_back(staff);
|
|
part->staves()->push_back(staff);
|
|
readStaff(staff, e);
|
|
}
|
|
else if (tag == "Instrument") {
|
|
Instrument* i = part->instrument();
|
|
readInstrument(i, part, e);
|
|
// add string data from MIDI program number, if possible
|
|
if (i->stringData()->strings() == 0
|
|
&& i->channel().count() > 0
|
|
&& i->drumset() == nullptr) {
|
|
int program = i->channel(0)->program();
|
|
if (program >= 24 && program <= 30) // guitars
|
|
i->setStringData(StringData(19, 6, g_guitarStrings));
|
|
else if ( (program >= 32 && program <= 39) || program == 43) // bass / double-bass
|
|
i->setStringData(StringData(24, 4, g_bassStrings));
|
|
else if (program == 40) // violin and other treble string instr.
|
|
i->setStringData(StringData(24, 4, g_violinStrings));
|
|
else if (program == 41) // viola and other alto string instr.
|
|
i->setStringData(StringData(24, 4, g_violaStrings));
|
|
else if (program == 42) // cello and other bass string instr.
|
|
i->setStringData(StringData(24, 4, g_celloStrings));
|
|
}
|
|
Drumset* d = i->drumset();
|
|
Staff* st = part->staff(0);
|
|
if (d && st && st->lines(Fraction(0,1)) != 5) {
|
|
int n = 0;
|
|
if (st->lines(Fraction(0,1)) == 1)
|
|
n = 4;
|
|
for (int j = 0; j < DRUM_INSTRUMENTS; ++j)
|
|
d->drum(j).line -= n;
|
|
}
|
|
}
|
|
else if (tag == "name") {
|
|
Text* t = new Text(_score);
|
|
readText114(e, t, t);
|
|
part->instrument()->setLongName(t->xmlText());
|
|
delete t;
|
|
}
|
|
else if (tag == "shortName") {
|
|
Text* t = new Text(_score);
|
|
readText114(e, t, t);
|
|
part->instrument()->setShortName(t->xmlText());
|
|
delete t;
|
|
}
|
|
else if (tag == "trackName")
|
|
part->setPartName(e.readElementText());
|
|
else if (tag == "show")
|
|
part->setShow(e.readInt());
|
|
else
|
|
e.unknown();
|
|
}
|
|
if (part->partName().isEmpty())
|
|
part->setPartName(part->instrument()->trackName());
|
|
|
|
if (part->instrument()->useDrumset()) {
|
|
for (Staff* staff : *part->staves()) {
|
|
int lines = staff->lines(Fraction(0,1));
|
|
int bf = staff->barLineFrom();
|
|
int bt = staff->barLineTo();
|
|
staff->setStaffType(Fraction(0,1), *StaffType::getDefaultPreset(StaffGroup::PERCUSSION));
|
|
|
|
// this allows 2/3-line percussion staves to keep the double spacing they had in 1.3
|
|
|
|
if (lines == 2 || lines == 3)
|
|
((StaffType*)(staff->staffType(Fraction(0,1))))->setLineDistance(Spatium(2.0));
|
|
|
|
staff->setLines(Fraction(0,1), lines); // this also sets stepOffset
|
|
staff->setBarLineFrom(bf);
|
|
staff->setBarLineTo(bt);
|
|
}
|
|
}
|
|
//set default articulations
|
|
QList<MidiArticulation> articulations;
|
|
articulations.append(MidiArticulation("", "", 100, 100));
|
|
articulations.append(MidiArticulation("staccato", "", 100, 50));
|
|
articulations.append(MidiArticulation("tenuto", "", 100, 100));
|
|
articulations.append(MidiArticulation("sforzato", "", 120, 100));
|
|
part->instrument()->setArticulation(articulations);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readPageFormat
|
|
//---------------------------------------------------------
|
|
|
|
static void readPageFormat(PageFormat* pf, XmlReader& e)
|
|
{
|
|
qreal _oddRightMargin = 0.0;
|
|
qreal _evenRightMargin = 0.0;
|
|
bool landscape = false;
|
|
QString type;
|
|
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "landscape")
|
|
landscape = e.readInt();
|
|
else 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();
|
|
}
|
|
pf->setTwosided(type == "odd" || type == "even");
|
|
if (type == "odd" || type == "both") {
|
|
pf->setOddLeftMargin(lm);
|
|
_oddRightMargin = rm;
|
|
pf->setOddTopMargin(tm);
|
|
pf->setOddBottomMargin(bm);
|
|
}
|
|
if (type == "even" || type == "both") {
|
|
pf->setEvenLeftMargin(lm);
|
|
_evenRightMargin = rm;
|
|
pf->setEvenTopMargin(tm);
|
|
pf->setEvenBottomMargin(bm);
|
|
}
|
|
}
|
|
else if (tag == "page-height")
|
|
pf->setSize(QSizeF(pf->size().width(), e.readDouble() * 0.5 / PPI));
|
|
else if (tag == "page-width")
|
|
pf->setSize(QSizeF(e.readDouble() * 0.5 / PPI, pf->size().height()));
|
|
else if (tag == "pageFormat") {
|
|
const PaperSize* s = getPaperSize114(e.readElementText());
|
|
pf->setSize(QSizeF(s->w, s->h));
|
|
}
|
|
else if (tag == "page-offset") {
|
|
e.readInt();
|
|
}
|
|
else
|
|
e.unknown();
|
|
}
|
|
if (landscape)
|
|
pf->setSize(pf->size().transposed());
|
|
qreal w1 = pf->size().width() - pf->oddLeftMargin() - _oddRightMargin;
|
|
qreal w2 = pf->size().width() - pf->evenLeftMargin() - _evenRightMargin;
|
|
pf->setPrintableWidth(qMin(w1, w2)); // silently adjust right margins
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readStyle
|
|
//---------------------------------------------------------
|
|
|
|
static void readStyle(MStyle* style, XmlReader& e)
|
|
{
|
|
QString oldChordDescriptionFile = style->value(Sid::chordDescriptionFile).toString();
|
|
bool chordListTag = false;
|
|
while (e.readNextStartElement()) {
|
|
QString tag = e.name().toString();
|
|
|
|
if (tag == "lyricsDistance") // was renamed
|
|
tag = "lyricsPosBelow";
|
|
|
|
if (tag == "TextStyle") {
|
|
// TextStyle s;
|
|
//TODO s.read(e);
|
|
// style->setTextStyle(s);
|
|
e.skipCurrentElement();
|
|
}
|
|
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 == "Spatium")
|
|
style->set(Sid::spatium, e.readDouble() * DPMM);
|
|
else if (tag == "page-layout") {
|
|
PageFormat pf;
|
|
initPageFormat(style, &pf);
|
|
pf.read(e);
|
|
setPageFormat(style, pf);
|
|
}
|
|
else if (tag == "displayInConcertPitch")
|
|
style->set(Sid::concertPitch, QVariant(bool(e.readInt())));
|
|
else if (tag == "ChordList") {
|
|
style->chordList()->clear();
|
|
style->chordList()->read(e);
|
|
for (ChordFont f : style->chordList()->fonts) {
|
|
if (f.family == "MuseJazz") {
|
|
f.family = "MuseJazz Text";
|
|
}
|
|
}
|
|
style->setCustomChordList(true);
|
|
chordListTag = true;
|
|
}
|
|
else if (tag == "pageFillLimit" || tag == "genTimesig" || tag == "FixMeasureNumbers" || tag == "FixMeasureWidth") // obsolete
|
|
e.skipCurrentElement();
|
|
else if (tag == "systemDistance") // obsolete
|
|
style->set(Sid::minSystemDistance, QVariant(e.readDouble()));
|
|
else if (tag == "stemDir") {
|
|
int voice = e.attribute("voice", "1").toInt() - 1;
|
|
switch(voice) {
|
|
case 0: tag = "StemDir1"; break;
|
|
case 1: tag = "StemDir2"; break;
|
|
case 2: tag = "StemDir3"; break;
|
|
case 3: tag = "StemDir4"; break;
|
|
}
|
|
}
|
|
// for compatibility:
|
|
else if (tag == "oddHeader" || tag == "evenHeader" || tag == "oddFooter" || tag == "evenFooter")
|
|
tag += "C";
|
|
#if 0 // TODO-ws
|
|
int idx2;
|
|
for (idx2 = 0; idx2 < int(ArticulationType::ARTICULATIONS); ++idx2) {
|
|
ArticulationInfo& ai = Articulation::articulationList[idx2];
|
|
// deal with obsolete tags from 1.14 format
|
|
if (tag == "SforzatoaccentAnchor")
|
|
tag = "SforzatoAnchor";
|
|
if (tag == "SnappizzicatorAnchor")
|
|
tag = "SnappizzicatoAnchor";
|
|
else if (tag == "EspressivoAnchor")
|
|
continue;
|
|
if (QString::compare(tag, QString(ai.name).append("Anchor"), Qt::CaseInsensitive) == 0
|
|
|| QString::compare(tag, QString("U").append(ai.name).append("Anchor"), Qt::CaseInsensitive) == 0
|
|
|| QString::compare(tag, QString("D").append(ai.name).append("Anchor"), Qt::CaseInsensitive) == 0
|
|
) {
|
|
Sid si = MStyle::styleIdx(QString(ai.name).append("Anchor"));
|
|
if (si != Sid::NOSTYLE) {
|
|
QString val(e.readElementText());
|
|
style->set(si, val.toInt());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (idx2 < int(ArticulationType::ARTICULATIONS))
|
|
continue;
|
|
#endif
|
|
else {
|
|
if (!style->readProperties(e)) {
|
|
e.skipCurrentElement();
|
|
}
|
|
}
|
|
//TODO style->convertToUnit(tag, val);
|
|
}
|
|
|
|
// 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();
|
|
#if 0 // TODO
|
|
//
|
|
// Compatibility with old scores/styles:
|
|
// translate old frameWidthMM and paddingWidthMM
|
|
// into spatium units
|
|
//
|
|
int n = style->textStyles().size();
|
|
qreal _spatium = style->value(Sid::spatium).toDouble();
|
|
qreal spMM = _spatium / DPMM;
|
|
for (int i = 0; i < n; ++i) {
|
|
TextStyle* s = &style->textStyle(StyledPropertyListIdx(i));
|
|
if (s->frameWidthMM() != 0.0)
|
|
s->setFrameWidth(Spatium(s->frameWidthMM() / spMM));
|
|
if (s->paddingWidthMM() != 0.0)
|
|
s->setPaddingWidth(Spatium(s->paddingWidthMM() / spMM));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// read114
|
|
// import old version <= 1.3 files
|
|
//---------------------------------------------------------
|
|
|
|
Score::FileError MasterScore::read114(XmlReader& e)
|
|
{
|
|
for (unsigned int i = 0; i < sizeof(style114)/sizeof(*style114); ++i)
|
|
style().set(style114[i].sid, style114[i].val);
|
|
#if 0
|
|
// old text style defaults
|
|
TextStyle ts = style().textStyle("Chord Symbol");
|
|
ts.setYoff(-4.0);
|
|
style().setTextStyle(ts);
|
|
ts = style().textStyle("Rehearsal Mark");
|
|
ts.setSquare(false);
|
|
ts.setFrameRound(20);
|
|
style().setTextStyle(ts);
|
|
ts = style().textStyle("Dynamics");
|
|
ts.setItalic(false);
|
|
style().setTextStyle(ts);
|
|
#endif
|
|
TempoMap tm;
|
|
while (e.readNextStartElement()) {
|
|
e.setTrack(-1);
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "Staff")
|
|
readStaffContent(this, e);
|
|
else if (tag == "KeySig") { // not supported
|
|
KeySig* ks = new KeySig(this);
|
|
ks->read(e);
|
|
delete ks;
|
|
}
|
|
else if (tag == "siglist")
|
|
_sigmap->read(e, _fileDivision);
|
|
else if (tag == "programVersion") {
|
|
setMscoreVersion(e.readElementText());
|
|
parseVersion(mscoreVersion());
|
|
}
|
|
else if (tag == "programRevision")
|
|
setMscoreRevision(e.readInt());
|
|
else if (tag == "Mag"
|
|
|| tag == "MagIdx"
|
|
|| tag == "xoff"
|
|
|| tag == "Symbols"
|
|
|| tag == "cursorTrack"
|
|
|| tag == "yoff")
|
|
e.skipCurrentElement(); // obsolete
|
|
else if (tag == "tempolist") {
|
|
// store the tempo list to create invisible tempo text later
|
|
qreal tempo = e.attribute("fix","2.0").toDouble();
|
|
tm.setRelTempo(tempo);
|
|
while (e.readNextStartElement()) {
|
|
if (e.name() == "tempo") {
|
|
int tick = e.attribute("tick").toInt();
|
|
double tmp = e.readElementText().toDouble();
|
|
tick = (tick * MScore::division + _fileDivision/2) / _fileDivision;
|
|
auto pos = tm.find(tick);
|
|
if (pos != tm.end())
|
|
tm.erase(pos);
|
|
tm.setTempo(tick, tmp);
|
|
}
|
|
else if (e.name() == "relTempo")
|
|
e.readElementText();
|
|
else
|
|
e.unknown();
|
|
}
|
|
}
|
|
else if (tag == "playMode")
|
|
setPlayMode(PlayMode(e.readInt()));
|
|
else if (tag == "SyntiSettings")
|
|
_synthesizerState.read(e);
|
|
else if (tag == "Spatium")
|
|
setSpatium (e.readDouble() * DPMM);
|
|
else if (tag == "Division")
|
|
_fileDivision = e.readInt();
|
|
else if (tag == "showInvisible")
|
|
setShowInvisible(e.readInt());
|
|
else if (tag == "showFrames")
|
|
setShowFrames(e.readInt());
|
|
else if (tag == "showMargins")
|
|
setShowPageborders(e.readInt());
|
|
else if (tag == "Style") {
|
|
qreal sp = spatium();
|
|
readStyle(&style(), e);
|
|
//style()->load(e);
|
|
// adjust this now so chords render properly on read
|
|
// other style adjustments can wait until reading is finished
|
|
if (styleB(Sid::useGermanNoteNames))
|
|
style().set(Sid::useStandardNoteNames, false);
|
|
if (_layoutMode == LayoutMode::FLOAT) {
|
|
// style should not change spatium in
|
|
// float mode
|
|
setSpatium(sp);
|
|
}
|
|
}
|
|
else if (tag == "TextStyle") {
|
|
e.skipCurrentElement();
|
|
#if 0 // TODO
|
|
TextStyle s;
|
|
s.read(e);
|
|
|
|
qreal spMM = spatium() / DPMM;
|
|
if (s.frameWidthMM() != 0.0)
|
|
s.setFrameWidth(Spatium(s.frameWidthMM() / spMM));
|
|
if (s.paddingWidthMM() != 0.0)
|
|
s.setPaddingWidth(Spatium(s.paddingWidthMM() / spMM));
|
|
|
|
// convert 1.2 text styles
|
|
s.setName(convertOldTextStyleNames(s.name()));
|
|
if (s.family() == "MuseJazz")
|
|
s.setFamily("MuseJazz Text");
|
|
|
|
if (s.name() == "Lyrics Odd Lines" || s.name() == "Lyrics Even Lines")
|
|
s.setAlign(Align(int(s.align()) & int(~Align::VMASK)) | Align::BASELINE);
|
|
|
|
style().setTextStyle(s);
|
|
#endif
|
|
}
|
|
else if (tag == "page-layout") {
|
|
PageFormat pf;
|
|
readPageFormat(&pf, e);
|
|
}
|
|
else if (tag == "copyright" || tag == "rights") {
|
|
Text* text = new Text(this);
|
|
readText114(e, text, text);
|
|
setMetaTag("copyright", text->plainText());
|
|
delete text;
|
|
}
|
|
else if (tag == "movement-number")
|
|
setMetaTag("movementNumber", e.readElementText());
|
|
else if (tag == "movement-title")
|
|
setMetaTag("movementTitle", e.readElementText());
|
|
else if (tag == "work-number")
|
|
setMetaTag("workNumber", e.readElementText());
|
|
else if (tag == "work-title")
|
|
setMetaTag("workTitle", e.readElementText());
|
|
else if (tag == "source")
|
|
setMetaTag("source", e.readElementText());
|
|
else if (tag == "metaTag") {
|
|
QString name = e.attribute("name");
|
|
setMetaTag(name, e.readElementText());
|
|
}
|
|
else if (tag == "Part") {
|
|
Part* part = new Part(this);
|
|
readPart(part, e);
|
|
parts().push_back(part);
|
|
}
|
|
else if (tag == "Slur") {
|
|
Slur* slur = new Slur(this);
|
|
readSlur206(e, slur);
|
|
addSpanner(slur);
|
|
}
|
|
else if ((tag == "HairPin")
|
|
|| (tag == "Ottava")
|
|
|| (tag == "TextLine")
|
|
|| (tag == "Volta")
|
|
|| (tag == "Trill")
|
|
|| (tag == "Pedal")) {
|
|
Spanner* s = toSpanner(Element::name2Element(tag, this));
|
|
if (tag == "Volta")
|
|
readVolta114(e, toVolta(s));
|
|
else if (tag == "Ottava")
|
|
readOttava114(e, toOttava(s));
|
|
else if (tag == "TextLine")
|
|
readTextLine114(e, toTextLine(s));
|
|
else if (tag == "Pedal")
|
|
readPedal114(e, toPedal(s));
|
|
else if (tag == "Trill")
|
|
readTrill206(e, toTrill(s));
|
|
else {
|
|
Q_ASSERT(tag == "HairPin");
|
|
readHairpin206(e, toHairpin(s));
|
|
}
|
|
if (s->track() == -1)
|
|
s->setTrack(e.track());
|
|
else
|
|
e.setTrack(s->track()); // update current track
|
|
if (s->tick() == Fraction(-1,1))
|
|
s->setTick(e.tick());
|
|
else
|
|
e.setTick(s->tick()); // update current tick
|
|
if (s->track2() == -1)
|
|
s->setTrack2(s->track());
|
|
if (s->ticks().isZero()) {
|
|
qDebug("zero spanner %s ticks: %d", s->name(), s->ticks().ticks());
|
|
delete s;
|
|
}
|
|
else {
|
|
addSpanner(s);
|
|
}
|
|
}
|
|
else if (tag == "Excerpt") {
|
|
if (MScore::noExcerpts)
|
|
e.skipCurrentElement();
|
|
else {
|
|
Excerpt* ex = new Excerpt(this);
|
|
ex->read(e);
|
|
_excerpts.append(ex);
|
|
}
|
|
}
|
|
else if (tag == "Beam") {
|
|
Beam* beam = new Beam(this);
|
|
beam->read(e);
|
|
beam->setParent(0);
|
|
// _beams.append(beam);
|
|
}
|
|
else if (tag == "name")
|
|
setName(e.readElementText());
|
|
else
|
|
e.unknown();
|
|
}
|
|
|
|
if (e.error() != QXmlStreamReader::NoError) {
|
|
qDebug("%lld %lld: %s ", e.lineNumber(), e.columnNumber(), qPrintable(e.errorString()));
|
|
return FileError::FILE_BAD_FORMAT;
|
|
}
|
|
|
|
for (Staff* s : staves()) {
|
|
int idx = s->idx();
|
|
int track = idx * VOICES;
|
|
|
|
// check barLineSpan
|
|
if (s->barLineSpan() > (nstaves() - idx)) {
|
|
qDebug("read114: invalid barline span %d (max %d)",
|
|
s->barLineSpan(), nstaves() - idx);
|
|
s->setBarLineSpan(nstaves() - idx);
|
|
}
|
|
for (auto i : s->clefList()) {
|
|
Fraction tick = Fraction::fromTicks(i.first);
|
|
ClefType clefId = i.second._concertClef;
|
|
Measure* m = tick2measure(tick);
|
|
if (!m)
|
|
continue;
|
|
SegmentType st = SegmentType::Clef;
|
|
if (tick == m->tick()) {
|
|
if (m->prevMeasure())
|
|
m = m->prevMeasure();
|
|
else
|
|
st = SegmentType::HeaderClef;
|
|
}
|
|
Segment* seg = m->getSegment(st, tick);
|
|
if (seg->element(track))
|
|
seg->element(track)->setGenerated(false);
|
|
else {
|
|
Clef* clef = new Clef(this);
|
|
clef->setClefType(clefId);
|
|
clef->setTrack(track);
|
|
clef->setParent(seg);
|
|
clef->setGenerated(false);
|
|
seg->add(clef);
|
|
}
|
|
}
|
|
|
|
// create missing KeySig
|
|
KeyList* km = s->keyList();
|
|
for (auto i = km->begin(); i != km->end(); ++i) {
|
|
Fraction tick = Fraction::fromTicks(i->first);
|
|
if (tick < Fraction(0,1)) {
|
|
qDebug("read114: Key tick %d", tick.ticks());
|
|
continue;
|
|
}
|
|
if (tick.isZero() && i->second.key() == Key::C)
|
|
continue;
|
|
Measure* m = tick2measure(tick);
|
|
if (!m) //empty score
|
|
break;
|
|
Segment* seg = m->getSegment(SegmentType::KeySig, tick);
|
|
if (seg->element(track))
|
|
toKeySig(seg->element(track))->setGenerated(false);
|
|
else {
|
|
KeySigEvent ke = i->second;
|
|
KeySig* ks = new KeySig(this);
|
|
ks->setKeySigEvent(ke);
|
|
ks->setParent(seg);
|
|
ks->setTrack(track);
|
|
ks->setGenerated(false);
|
|
seg->add(ks);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (std::pair<int,Spanner*> p : spanner()) {
|
|
Spanner* s = p.second;
|
|
if (!s->isSlur()) {
|
|
if (s->isVolta()) {
|
|
Volta* volta = toVolta(s);
|
|
volta->setAnchor(Spanner::Anchor::MEASURE);
|
|
}
|
|
}
|
|
|
|
if (s->isOttava() || s->isPedal() || s->isTrill() || s->isTextLine()) {
|
|
qreal yo = 0;
|
|
if (s->isOttava()) {
|
|
// fix ottava position
|
|
yo = styleValue(Pid::OFFSET, Sid::ottavaPosAbove).toPointF().y();
|
|
if (s->placeBelow())
|
|
yo = -yo + s->staff()->height();
|
|
}
|
|
else if (s->isPedal()) {
|
|
yo = styleValue(Pid::OFFSET, Sid::pedalPosBelow).toPointF().y();
|
|
}
|
|
else if (s->isTrill()) {
|
|
yo = styleValue(Pid::OFFSET, Sid::trillPosAbove).toPointF().y();
|
|
}
|
|
else if (s->isTextLine()) {
|
|
yo = -5.0 * spatium();
|
|
}
|
|
if (!s->spannerSegments().empty()) {
|
|
for (SpannerSegment* seg : s->spannerSegments()) {
|
|
if (!seg->offset().isNull())
|
|
seg->ryoffset() = seg->offset().y() - yo;
|
|
}
|
|
}
|
|
else {
|
|
s->ryoffset() = -yo;
|
|
}
|
|
}
|
|
}
|
|
|
|
connectTies();
|
|
|
|
//
|
|
// remove "middle beam" flags from first ChordRest in
|
|
// measure
|
|
//
|
|
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
|
|
int tracks = nstaves() * VOICES;
|
|
bool first = true;
|
|
for (int track = 0; track < tracks; ++track) {
|
|
for (Segment* s = m->first(); s; s = s->next()) {
|
|
if (s->segmentType() != SegmentType::ChordRest)
|
|
continue;
|
|
ChordRest* cr = toChordRest(s->element(track));
|
|
if (cr) {
|
|
#if 0 // TODO
|
|
if (cr->isRest()) {
|
|
Rest* r = toRest(cr);
|
|
if (!r->offset().isNull()) {
|
|
int lineOffset = r->computeLineOffset();
|
|
qreal lineDist = r->staff() ? r->staff()->staffType(cr->tick())->lineDistance().val() : 1.0;
|
|
r->rUserYoffset() -= (lineOffset * .5 * lineDist * r->spatium());
|
|
}
|
|
}
|
|
#endif
|
|
if (!first) {
|
|
switch (cr->beamMode()) {
|
|
case Beam::Mode::AUTO:
|
|
case Beam::Mode::BEGIN:
|
|
case Beam::Mode::END:
|
|
case Beam::Mode::NONE:
|
|
break;
|
|
case Beam::Mode::MID:
|
|
case Beam::Mode::BEGIN32:
|
|
case Beam::Mode::BEGIN64:
|
|
cr->setBeamMode(Beam::Mode::BEGIN);
|
|
break;
|
|
case Beam::Mode::INVALID:
|
|
if (cr->isChord())
|
|
cr->setBeamMode(Beam::Mode::AUTO);
|
|
else
|
|
cr->setBeamMode(Beam::Mode::NONE);
|
|
break;
|
|
}
|
|
first = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (MeasureBase* mb = first(); mb; mb = mb->next()) {
|
|
if (mb->isVBox()) {
|
|
VBox* b = toVBox(mb);
|
|
qreal y = styleP(Sid::staffUpperBorder);
|
|
b->setBottomGap(y);
|
|
}
|
|
}
|
|
|
|
_fileDivision = MScore::division;
|
|
|
|
//
|
|
// sanity check for barLineSpan and update ottavas
|
|
//
|
|
for (Staff* staff : staves()) {
|
|
int barLineSpan = staff->barLineSpan();
|
|
int idx = staff->idx();
|
|
int n = nstaves();
|
|
if (idx + barLineSpan > n) {
|
|
qDebug("bad span: idx %d span %d staves %d", idx, barLineSpan, n);
|
|
staff->setBarLineSpan(n - idx);
|
|
}
|
|
staff->updateOttava();
|
|
}
|
|
|
|
// adjust some styles
|
|
if (styleB(Sid::hideEmptyStaves)) // http://musescore.org/en/node/16228
|
|
style().set(Sid::dontHideStavesInFirstSystem, false);
|
|
if (styleB(Sid::showPageNumberOne)) { // http://musescore.org/en/node/21207
|
|
style().set(Sid::evenFooterL, QString("$P"));
|
|
style().set(Sid::oddFooterR, QString("$P"));
|
|
}
|
|
if (styleI(Sid::minEmptyMeasures) == 0)
|
|
style().set(Sid::minEmptyMeasures, 1);
|
|
style().set(Sid::frameSystemDistance, styleS(Sid::frameSystemDistance) + Spatium(6.0));
|
|
// hack: net overall effect of layout changes has been for things to take slightly more room
|
|
qreal adjustedSpacing = qMax(styleD(Sid::measureSpacing) * 0.95, 1.0);
|
|
style().set(Sid::measureSpacing, adjustedSpacing);
|
|
|
|
_showOmr = false;
|
|
|
|
// add invisible tempo text if necessary
|
|
// some 1.3 scores have tempolist but no tempo text
|
|
fixTicks();
|
|
for (auto i : tm) {
|
|
Fraction tick = Fraction::fromTicks(i.first);
|
|
qreal tempo = i.second.tempo;
|
|
if (tempomap()->tempo(tick.ticks()) != tempo) {
|
|
TempoText* tt = new TempoText(this);
|
|
tt->setXmlText(QString("<sym>metNoteQuarterUp</sym> = %1").arg(qRound(tempo*60)));
|
|
tt->setTempo(tempo);
|
|
tt->setTrack(0);
|
|
tt->setVisible(false);
|
|
Measure* m = tick2measure(tick);
|
|
if (m) {
|
|
Segment* seg = m->getSegment(SegmentType::ChordRest, tick);
|
|
seg->add(tt);
|
|
setTempo(tick, tempo);
|
|
}
|
|
else
|
|
delete tt;
|
|
}
|
|
}
|
|
|
|
// create excerpts
|
|
|
|
QList<Excerpt*> readExcerpts;
|
|
readExcerpts.swap(_excerpts);
|
|
for (Excerpt* excerpt : readExcerpts) {
|
|
if (excerpt->parts().isEmpty()) // ignore empty parts
|
|
continue;
|
|
if (!excerpt->parts().isEmpty()) {
|
|
_excerpts.push_back(excerpt);
|
|
Score* nscore = new Score(this);
|
|
excerpt->setPartScore(nscore);
|
|
nscore->style().set(Sid::createMultiMeasureRests, true);
|
|
Excerpt::createExcerpt(excerpt);
|
|
}
|
|
}
|
|
|
|
// volta offsets in older scores are hardcoded to be relative to a voltaY of -2.0sp
|
|
// we'll force this and live with it for the score
|
|
// but we wait until now to do it so parts don't have this issue
|
|
|
|
if (styleV(Sid::voltaPosAbove) == MScore::baseStyle().value(Sid::voltaPosAbove))
|
|
style().set(Sid::voltaPosAbove, QPointF(0.0, -2.0f));
|
|
|
|
fixTicks();
|
|
rebuildMidiMapping();
|
|
updateChannel();
|
|
|
|
// treat reading a 1.14 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;
|
|
}
|
|
|
|
}
|
|
|