MuseScore/libmscore/read114.cpp
Mark McKay fdeb795e34 fix #275313: rework mixer ui 2
Moving PartEditBase into separate file.  Creating new files for
building mixer.

Creating art assets/UI design for new components.

Styling the track control.

Adding track area.

Separating out score from update.

Creating instances of mixer UI.

Creating part per voice now.

Can click on tracks to select them now.

Can now switch bwtewwn tracks.

Setting patch channel now.

Setting enabled off when no track selected.

Improving slider ui.

Turning Channel into a class and adding listener to it.

Somewhat stabalized sharing track objects between interfaces.

Can now apply volume changes to both expanded and collapsed tracks.

Pan knob is now working.

Encapsulating the rest of the fields in Channel.

Mute and solo now working.

Reverb and chorus now working.

Drumkit checkbox now working.  Port and channel somewhat working.

Adding support for colors per track.

Part name change now working.

Separating out MixerTrackItem

Finishing moving MixerTrackItem to new file.

Cleaning up code.

Moving PartEditBase into separate file.  Creating new files for
building mixer.

Creating art assets/UI design for new components.

Styling the track control.

Adding track area.

Separating out score from update.

Creating instances of mixer UI.

Creating part per voice now.

Can click on tracks to select them now.

Can now switch bwtewwn tracks.

Setting patch channel now.

Setting enabled off when no track selected.

Improving slider ui.

Turning Channel into a class and adding listener to it.

Somewhat stabalized sharing track objects between interfaces.

Can now apply volume changes to both expanded and collapsed tracks.

Pan knob is now working.

Encapsulating the rest of the fields in Channel.

Mute and solo now working.

Reverb and chorus now working.

Drumkit checkbox now working.  Port and channel somewhat working.

Adding support for colors per track.

Part name change now working.

Separating out MixerTrackItem

Finishing moving MixerTrackItem to new file.

Cleaning up code.

Setting color in collapsed mode now affects all channels.

Using shared_ptr to track MixerTrackItem.  Part changes now affect
all instruments.

Creating new track UI object to handle parts.

Using shard_ptr to track MixerTrackItem objects.

setting port and channel data now.

Changing to horizontal layout.

Fixing knob display.  Chaning track control appearance.

Setting init slider window size.

Switchong back to vertical orientation.  Fixing a few UI bugs in
the slider.

Tracks now left aligned.

Moving details panel above mixer.  Now changing track selection when
user clicks on sliders.

Pan and volume controls now reflect track color.

Showing volume and pan values in tooltips.

Creating a new slider control for mixer.

Switching Channel's volume, pan, reverb and chorus and chaning them
to doubles with a decimal range.

No longer writing out vol, pan, chor, reverb when at default values.

Nolonger writing vol, pan, chorus, reverb as controler values in
output file.

Now testing against default values on write.

More export fixes.

Manually editing test files to reflect new channel parameters.

Manually editing more test files to reflect new channel parameters.

Manually editing more test files to reflect new channel parameters.

More test changes to make Travis happy.

More test changes to make Travis happy.

Importing MusicXML now matches new volume, pan ranges.

Changing range of pan.  Fixing a few bugs with calculating MIDI.
Altering test files for Travis.

fix #275313: rework-mixer-ui-2

Moving PartEditBase into separate file. Creating new files for
building mixer.

Creating art assets/UI design for new components.

Styling the track control.

Adding track area.

Separating out score from update.

Creating instances of mixer UI.

Creating part per voice now.

Can click on tracks to select them now.

Can now switch bwtewwn tracks.

Setting patch channel now.

Setting enabled off when no track selected.

Improving slider ui.

Turning Channel into a class and adding listener to it.

Somewhat stabalized sharing track objects between interfaces.

Can now apply volume changes to both expanded and collapsed tracks.

Pan knob is now working.

Encapsulating the rest of the fields in Channel.

Mute and solo now working.

Reverb and chorus now working.

Drumkit checkbox now working.  Port and channel somewhat working.

Adding support for colors per track.

Part name change now working.

Separating out MixerTrackItem

Finishing moving MixerTrackItem to new file.

Cleaning up code.

Moving PartEditBase into separate file.  Creating new files for
building mixer.

Creating art assets/UI design for new components.

Styling the track control.

Adding track area.

Separating out score from update.

Creating instances of mixer UI.

Creating part per voice now.

Can click on tracks to select them now.

Can now switch bwtewwn tracks.

Setting patch channel now.

Setting enabled off when no track selected.

Improving slider ui.

Turning Channel into a class and adding listener to it.

Somewhat stabalized sharing track objects between interfaces.

Can now apply volume changes to both expanded and collapsed tracks.

Pan knob is now working.

Encapsulating the rest of the fields in Channel.

Mute and solo now working.

Reverb and chorus now working.

Drumkit checkbox now working.  Port and channel somewhat working.

Adding support for colors per track.

Part name change now working.

Separating out MixerTrackItem

Finishing moving MixerTrackItem to new file.

Cleaning up code.

Setting color in collapsed mode now affects all channels.

Using shared_ptr to track MixerTrackItem.  Part changes now affect
all instruments.

Creating new track UI object to handle parts.

Using shard_ptr to track MixerTrackItem objects.

setting port and channel data now.

Changing to horizontal layout.

Fixing knob display.  Chaning track control appearance.

Setting init slider window size.

Switchong back to vertical orientation.  Fixing a few UI bugs in
the slider.

Tracks now left aligned.

Moving details panel above mixer.  Now changing track selection when
user clicks on sliders.

Pan and volume controls now reflect track color.

Showing volume and pan values in tooltips.

Creating a new slider control for mixer.

Switching Channel's volume, pan, reverb and chorus and chaning them
to doubles with a decimal range.

No longer writing out vol, pan, chor, reverb when at default values.

Nolonger writing vol, pan, chorus, reverb as controler values in
output file.

Now testing against default values on write.

More export fixes.

Manually editing test files to reflect new channel parameters.

Manually editing more test files to reflect new channel parameters.

Manually editing more test files to reflect new channel parameters.

More test changes to make Travis happy.

More test changes to make Travis happy.

Importing MusicXML now matches new volume, pan ranges.

Changing range of pan.  Fixing a few bugs with calculating MIDI.
Altering test files for Travis.

Restoring the volume, pan, chorus, reverb to original char data type
& range.  UI now shows different 'user friendly' ranges.

Overwriting tests with versions from master.

mtest/libmscore/compat114/clef_missing_first-ref.mscx
mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx
mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml
mtest/musicxml/io/testUninitializedDivisions_ref.xml

Restoring test files to original state.

Restoring test files to original state.

Restoring old values for importing files.

Restoring part methods.

mtest/importmidi/simplify_8th_dotted_no_staccato.mscx
mtest/libmscore/compat114/clef_missing_first-ref.mscx
mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx
mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml
mtest/musicxml/io/testUninitializedDivisions_ref.xml

Rearranging UI components for better feel.

Improving UI.  Fixed crash when changing part name.

Adding support for two lighting modes.  Showing part name over
channel expansion.

Adding master gain control to mixer.

Changing color of gain slider.

Adapting to latest source in main.

Changing master gain slider to use decibel calculation.

CSS now set on tracks whenever a Paint event received.

Restoring mixer slider values to refect MIDI ranges.  Fixing crash when drumkit checked.

Fixing crash when closing score.

Fixing alignment in mixer details.

Tweaking UI for better appearance.
2018-11-13 12:43:19 -05:00

3264 lines
141 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::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());
else
note->setOnTimeOffset(e.readInt() * 10);
}
else if (tag == "offTimeOffset") {
if (note->offTimeType() == 1)
note->setOffTimeOffset(1000 + (e.readInt() * 1000 / note->chord()->actualTicks()));
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()))) {
int tick = note->chord() ? note->chord()->tick() : -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(e.readInt());
else if (!readTupletProperties206(e, tuplet))
e.unknown();
}
Fraction r = (tuplet->ratio() == 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->setDuration(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->setDuration(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());
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.initTick(m->score()->fileDivision(e.intAttribute("tick")));
if (e.hasAttribute("len")) {
QStringList sl = e.attribute("len").split('/');
if (sl.size() == 2)
m->setLen(Fraction(sl[0].toInt(), sl[1].toInt()));
else
qDebug("illegal measure size <%s>", qPrintable(e.attribute("len")));
m->score()->sigmap()->add(m->tick(), SigEvent(m->len(), m->timesig()));
m->score()->sigmap()->add(m->endTick(), 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
int lastTick = e.tick();
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "move")
e.initTick(e.readFraction().ticks() + m->tick());
else if (tag == "tick") {
e.initTick(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:
// TODO 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();
int 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->globalDuration());
int pcrticks = pts.ticks();
pch->setDuration(Fraction::fromTicks(pcrticks / 2));
chord->setDuration(Fraction::fromTicks(crticks / 2));
}
else {
qDebug("tremolo: first note not found");
}
crticks /= 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->setDuration(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->duration().isValid()) // hack
rest->setDuration(m->timesig()/timeStretch);
lastTick = e.tick();
e.incTick(rest->actualTicks());
}
else if (tag == "Breath") {
Breath* breath = new Breath(m->score());
breath->setTrack(e.track());
int 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
int 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->actualDuration().isZero()) { // might happen with 1.3 scores
rm->setDuration(m->len());
}
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());
staff->clefList().insert(std::pair<int,ClefType>(e.tick(), 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(); 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
int 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);
int curTick = e.tick();
if (!ks->isCustom() && !ks->isAtonal() && ks->key() == Key::C && curTick == 0) {
// 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(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(iEndTick - e.tick());
// 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->setStaffSlashStyle(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.initTick(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()) {
int tupletTick = tuplet->tick();
int tupletDuration = tuplet->actualTicks() - 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;
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();
}
//---------------------------------------------------------
// 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.initTick(0);
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->setLen(ev.timesig());
measure->setTimesig(ev.nominal());
readMeasure(measure, staff, e);
measure->checkMeasure(staff);
if (!measure->isMMRest()) {
score->measures()->add(measure);
e.setLastMeasure(measure);
e.initTick(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.initTick(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.initTick(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(0, 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(0, 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);
i->channel()[0]->updateInitList();
}
}
//---------------------------------------------------------
// 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);
_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(0) != 5) {
int n = 0;
if (st->lines(0) == 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(0);
int bf = staff->barLineFrom();
int bt = staff->barLineTo();
staff->setStaffType(0, *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(0)))->setLineDistance(Spatium(2.0));
staff->setLines(0, 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 == "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 (!style->chordList()->loaded() && !chordListTag) {
if (style->value(Sid::chordsXmlFile).toBool())
style->chordList()->read("chords.xml");
style->chordList()->read(newChordDescriptionFile);
}
#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() == -1)
s->setTick(e.tick());
else
e.initTick(s->tick()); // update current tick
if (s->track2() == -1)
s->setTrack2(s->track());
if (s->ticks() == 0) {
qDebug("zero spanner %s ticks: %d", s->name(), s->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()) {
int tick = 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) {
int tick = i->first;
if (tick < 0) {
qDebug("read114: Key tick %d", tick);
continue;
}
if (tick == 0 && 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().isEmpty()) {
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
Spatium lmbd = styleS(Sid::lyricsMinBottomDistance);
style().set(Sid::lyricsMinBottomDistance, Spatium(lmbd.val() + 4.0));
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) {
int tick = i.first;
qreal tempo = i.second.tempo;
if (tempomap()->tempo(tick) != 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
for (Excerpt* excerpt : _excerpts) {
if (excerpt->parts().isEmpty()) { // ignore empty parts
_excerpts.removeOne(excerpt);
continue;
}
if (!excerpt->parts().isEmpty()) {
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;
}
}