LCOV - code coverage report
Current view: top level - src/importexport/capella/internal - capxml.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 73.9 % 846 625
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "capella.h"
      24              : 
      25              : //    CapXML import filter
      26              : //    Supports the CapXML 1.0 file format version 1.0.8 (capella 2008)
      27              : //    The implementation shares as much code as possible with the capella
      28              : //    (.cap) importer (capella.cpp and capella.h)
      29              : //    If statements in the parser match the element order in the schema definition
      30              : 
      31              : #include <assert.h>
      32              : #include <cmath>
      33              : 
      34              : #include <QRegularExpression>
      35              : 
      36              : #include "engraving/rw/xmlreader.h"
      37              : #include "engraving/engravingerrors.h"
      38              : #include "engraving/dom/masterscore.h"
      39              : #include "serialization/zipreader.h"
      40              : 
      41              : #include "log.h"
      42              : 
      43              : using namespace mu;
      44              : using namespace mu::engraving;
      45              : 
      46              : namespace mu::iex::capella {
      47              : //---------------------------------------------------------
      48              : //   capxReadFont
      49              : //---------------------------------------------------------
      50              : 
      51            6 : static QFont capxReadFont(XmlReader& e)
      52              : {
      53            6 :     QFont f;
      54            6 :     QString family = e.attribute("face");
      55            6 :     if (family != "") {
      56            6 :         f.setFamily(family);
      57              :     }
      58            6 :     qreal pointSize = e.doubleAttribute("height", 0);
      59            6 :     if (pointSize > 0.5) {
      60            6 :         f.setPointSizeF(pointSize);
      61              :     }
      62            6 :     int weight = e.intAttribute("weight");
      63            6 :     if (weight < 200) {
      64            0 :         f.setWeight(QFont::Light);
      65            6 :     } else if (weight < 550) {
      66            0 :         f.setWeight(QFont::Normal);
      67              :     } else {
      68            6 :         f.setWeight(QFont::Bold);
      69              :     }
      70            6 :     f.setItalic(e.asciiAttribute("italic", "false") == "true");
      71              :     // CAPELLA_TRACE("capxReadFont family '%s' ps %g w %d it '%s'", qPrintable(family), pointSize, weight, qPrintable(italic));
      72            6 :     e.readNext();
      73            6 :     return f;
      74            6 : }
      75              : 
      76              : //---------------------------------------------------------
      77              : //   qstring2timestep -- convert string to TIMESTEP
      78              : //---------------------------------------------------------
      79              : 
      80          366 : static bool qstring2timestep(QString& str, TIMESTEP& tstp)
      81              : {
      82          366 :     if (str == "1/1") {
      83            9 :         tstp = TIMESTEP::D1;
      84            9 :         return true;
      85          357 :     } else if (str == "1/2") {
      86            7 :         tstp = TIMESTEP::D2;
      87            7 :         return true;
      88          350 :     } else if (str == "1/4") {
      89          168 :         tstp = TIMESTEP::D4;
      90          168 :         return true;
      91          182 :     } else if (str == "1/8") {
      92           68 :         tstp = TIMESTEP::D8;
      93           68 :         return true;
      94          114 :     } else if (str == "1/16") {
      95          114 :         tstp = TIMESTEP::D16;
      96          114 :         return true;
      97            0 :     } else if (str == "1/32") {
      98            0 :         tstp = TIMESTEP::D32;
      99            0 :         return true;
     100            0 :     } else if (str == "1/64") {
     101            0 :         tstp = TIMESTEP::D64;
     102            0 :         return true;
     103            0 :     } else if (str == "1/128") {
     104            0 :         tstp = TIMESTEP::D128;
     105            0 :         return true;
     106            0 :     } else if (str == "2/1") {
     107            0 :         tstp = TIMESTEP::D_BREVE;
     108            0 :         return true;
     109              :     }
     110            0 :     return false;
     111              : }
     112              : 
     113              : //---------------------------------------------------------
     114              : //   BasicDrawObj::readCapx -- capx equivalent of BasicDrawObj::read
     115              : //---------------------------------------------------------
     116              : 
     117           19 : void BasicDrawObj::readCapx(XmlReader& e)
     118              : {
     119           19 :     nNotes = e.intAttribute("noteRange", 0);
     120              :     CAPELLA_TRACE("nNotes %d", nNotes);
     121           19 :     e.readNext();
     122           19 : }
     123              : 
     124              : //---------------------------------------------------------
     125              : //   BasicDurationalObj::readCapxDisplay -- capx equivalent of BasicDurationalObj::read
     126              : //   reads the <display> element only
     127              : //---------------------------------------------------------
     128              : 
     129            0 : void BasicDurationalObj::readCapxDisplay(XmlReader& e)
     130              : {
     131            0 :     invisible = e.attribute("invisible") == "true";
     132            0 :     e.readNext();
     133            0 : }
     134              : 
     135              : //---------------------------------------------------------
     136              : //   BasicDurationalObj::readCapx -- capx equivalent of BasicDurationalObj::read
     137              : //---------------------------------------------------------
     138              : 
     139          366 : void BasicDurationalObj::readCapx(XmlReader& e, unsigned int& fullm)
     140              : {
     141          366 :     nDots      = 0;
     142          366 :     noDuration = false;
     143          366 :     postGrace  = false;
     144          366 :     bSmall     = false;
     145          366 :     invisible  = false;
     146          366 :     notBlack   = false;
     147          366 :     color = Qt::black;
     148          366 :     t = TIMESTEP::D1;
     149          366 :     horizontalShift = 0;
     150          366 :     tupletDenominator = 0;
     151          366 :     tripartite   = 0;
     152          366 :     isProlonging = 0;
     153          366 :     QString base = e.attribute("base");
     154          366 :     bool res = false;
     155              :     // try to convert base to timestep ("first pattern")
     156          366 :     res = qstring2timestep(base, t);
     157          366 :     if (!res) {
     158              :         // try multi-measure rest ("second pattern")
     159            0 :         int i = base.toInt();
     160            0 :         if (i > 0) {
     161            0 :             fullm = i;
     162              :         } else {
     163            0 :             LOGD("BasicDurationalObj::readCapx: invalid base: '%s'", qPrintable(base));
     164              :         }
     165              :     }
     166          366 :     nDots = e.intAttribute("dots", 0);
     167          366 :     noDuration = e.asciiAttribute("noDuration", "false") == "true";
     168          502 :     while (e.readNextStartElement()) {
     169          136 :         const AsciiStringView tag(e.name());
     170          136 :         if (tag == "tuplet") {
     171          136 :             tupletDenominator = e.attribute("count").toInt();
     172          136 :             tripartite = e.asciiAttribute("tripartite", "false") == "true";
     173          136 :             isProlonging = e.asciiAttribute("prolong", "false") == "true";
     174          136 :             e.readNext();
     175              :         } else {
     176            0 :             e.unknown();
     177              :         }
     178              :     }
     179              :     CAPELLA_TRACE("DurationObj ndots %d nodur %d postgr %d bsm %d inv %d notbl %d t %d hsh %d den %d trp %d ispro %d fullm %d",
     180              :                   nDots, noDuration, postGrace, bSmall, invisible, notBlack, int(t), horizontalShift, tupletDenominator, tripartite,
     181              :                   isProlonging, fullm
     182              :                   );
     183          366 : }
     184              : 
     185              : //---------------------------------------------------------
     186              : //   BasicDurationalObj::readCapxObjectArray
     187              : //---------------------------------------------------------
     188              : 
     189           25 : void BasicDurationalObj::readCapxObjectArray(XmlReader& e)
     190              : {
     191           25 :     objects = cap->readCapxDrawObjectArray(e);
     192           25 : }
     193              : 
     194              : //---------------------------------------------------------
     195              : //   CapExplicitBarline::readCapx -- capx equivalent of CapExplicitBarline::read
     196              : //---------------------------------------------------------
     197              : 
     198            5 : void CapExplicitBarline::readCapx(XmlReader& e)
     199              : {
     200            5 :     AsciiStringView type = e.asciiAttribute("type", "single");
     201            5 :     if (type == "single") {
     202            3 :         _type = BarLineType::NORMAL;
     203            2 :     } else if (type == "double") {
     204            0 :         _type = BarLineType::DOUBLE;
     205            2 :     } else if (type == "end") {
     206            1 :         _type = BarLineType::END;
     207            1 :     } else if (type == "repEnd") {
     208            1 :         _type = BarLineType::END_REPEAT;
     209            0 :     } else if (type == "repBegin") {
     210            0 :         _type = BarLineType::START_REPEAT;
     211            0 :     } else if (type == "repEndBegin") {
     212            0 :         _type = BarLineType::END_START_REPEAT;
     213            0 :     } else if (type == "dashed") {
     214            0 :         _type = BarLineType::BROKEN;
     215              :     } else {
     216            0 :         _type = BarLineType::NORMAL;    // default
     217              :     }
     218            5 :     _barMode = 0;
     219            6 :     while (e.readNextStartElement()) {
     220            1 :         const AsciiStringView tag(e.name());
     221            1 :         if (tag == "drawObjects") {
     222            1 :             e.skipCurrentElement();
     223              :         } else {
     224            0 :             e.unknown();
     225              :         }
     226              :     }
     227            5 : }
     228              : 
     229              : //---------------------------------------------------------
     230              : //   CapClef::readCapx -- capx equivalent of CapClef::read
     231              : //---------------------------------------------------------
     232              : 
     233           43 : void CapClef::readCapx(XmlReader& e)
     234              : {
     235           43 :     QString clef = e.attribute("clef");
     236           43 :     if (clef == "G2-") {
     237            1 :         form = Form::G;
     238            1 :         line = ClefLine::L2;
     239            1 :         oct = Oct::OCT_BASSA;
     240           42 :     } else if (clef == "treble") {
     241           39 :         form = Form::G;
     242           39 :         line = ClefLine::L2;
     243           39 :         oct = Oct::OCT_NULL;
     244            3 :     } else if (clef == "bass") {
     245            3 :         form = Form::F;
     246            3 :         line = ClefLine::L4;
     247            3 :         oct = Oct::OCT_NULL;
     248              :     } else {
     249              :         /* default */
     250            0 :         form = Form::G;
     251            0 :         line = ClefLine::L2;
     252            0 :         oct = Oct::OCT_NULL;
     253              :     }
     254              :     CAPELLA_TRACE("Clef::read '%s' -> form %d line %d oct %d", qPrintable(clef), int(form), int(line), int(oct));
     255           43 :     e.readNext();
     256           43 : }
     257              : 
     258              : //---------------------------------------------------------
     259              : //   CapKey::readCapx -- capx equivalent of CapKey::read
     260              : //---------------------------------------------------------
     261              : 
     262            8 : void CapKey::readCapx(XmlReader& e)
     263              : {
     264            8 :     signature = e.intAttribute("fifths", 0);
     265              :     CAPELLA_TRACE("Key %d", signature);
     266            8 :     e.readNext();
     267            8 : }
     268              : 
     269              : //---------------------------------------------------------
     270              : //   qstring2timesig -- convert string to timesig
     271              : //   return true on success
     272              : //---------------------------------------------------------
     273              : 
     274           57 : static void qstring2timesig(QString& time, uchar& numerator, int& log2Denom, bool& allaBreve)
     275              : {
     276           57 :     bool res = true;
     277           57 :     numerator = 4;
     278           57 :     log2Denom = 2;
     279           57 :     allaBreve = false;                                 // set default
     280           57 :     if (time == "allaBreve") {
     281            0 :         numerator = 2;
     282            0 :         log2Denom = 1;
     283            0 :         allaBreve = true;
     284           57 :     } else if (time == "longAllaBreve") {
     285            0 :         numerator = 4;
     286            0 :         log2Denom = 1;
     287            0 :         allaBreve = true;
     288           57 :     } else if (time == "C") {
     289            0 :         numerator = 4;
     290            0 :         log2Denom = 2;
     291            0 :         allaBreve = false;
     292           57 :     } else if (time == "infinite") {
     293            0 :         LOGD("Meter infinite");
     294              :     }                                                            // not supported by MuseScore ?
     295              :     else {
     296           57 :         QStringList splitTime = time.split("/");
     297           57 :         if (splitTime.size() == 2) {
     298           57 :             numerator = splitTime.at(0).toInt();
     299           57 :             QString denom = splitTime.at(1);
     300           57 :             if (denom == "1") {
     301            0 :                 log2Denom = 0;
     302           57 :             } else if (denom == "2") {
     303            0 :                 log2Denom = 1;
     304           57 :             } else if (denom == "4") {
     305           51 :                 log2Denom = 2;
     306            6 :             } else if (denom == "8") {
     307            6 :                 log2Denom = 3;
     308            0 :             } else if (denom == "16") {
     309            0 :                 log2Denom = 4;
     310            0 :             } else if (denom == "32") {
     311            0 :                 log2Denom = 5;
     312            0 :             } else if (denom == "64") {
     313            0 :                 log2Denom = 6;
     314            0 :             } else if (denom == "128") {
     315            0 :                 log2Denom = 7;
     316              :             } else {
     317            0 :                 res = false;
     318              :             }
     319           57 :         } else {
     320            0 :             res = false;
     321              :         }
     322           57 :     }
     323              :     // TODO: recovery required if decoding the timesig failed ?
     324              :     CAPELLA_TRACE("Meter '%s' res %d %d/%d allaBreve %d", qPrintable(time), res, numerator, log2Denom, allaBreve);
     325           57 : }
     326              : 
     327              : //---------------------------------------------------------
     328              : //   CapMeter::readCapx -- capx equivalent of CapMeter::read
     329              : //---------------------------------------------------------
     330              : 
     331           14 : void CapMeter::readCapx(XmlReader& e)
     332              : {
     333              :     CAPELLA_TRACE("CapMeter::readCapx");
     334           14 :     QString time = e.attribute("time");
     335           14 :     qstring2timesig(time, numerator, log2Denom, allaBreve);
     336           14 :     e.readNext();
     337           14 : }
     338              : 
     339              : //---------------------------------------------------------
     340              : //   ChordObj::readCapxStem -- read <stem> element
     341              : //---------------------------------------------------------
     342              : 
     343            0 : void ChordObj::readCapxStem(XmlReader& e)
     344              : {
     345            0 :     QString dir = e.attribute("dir");
     346            0 :     if (dir == "up") {
     347            0 :         stemDir = StemDir::UP;
     348            0 :     } else if (dir == "down") {
     349            0 :         stemDir = StemDir::DOWN;
     350            0 :     } else if (dir == "none") {
     351            0 :         stemDir = StemDir::NONE;
     352              :     }
     353            0 :     e.readNext();
     354            0 : }
     355              : 
     356              : //---------------------------------------------------------
     357              : //   ChordObj::readCapxArticulation -- read <articulation> element
     358              : //---------------------------------------------------------
     359              : 
     360            0 : void ChordObj::readCapxArticulation(XmlReader& e)
     361              : {
     362            0 :     QString art = e.attribute("type");
     363            0 :     if (art == "staccato") {
     364            0 :         articulation = 1;
     365            0 :     } else if (art == "tenuto") {
     366            0 :         articulation = 2;
     367            0 :     } else if (art == "staccato tenuto") {
     368            0 :         articulation = 3;
     369            0 :     } else if (art == "staccatissimo") {
     370            0 :         articulation = 4;
     371            0 :     } else if (art == "normalAccent") {
     372            0 :         articulation = 5;
     373            0 :     } else if (art == "strongAccent") {
     374            0 :         articulation = 6;
     375            0 :     } else if (art == "weakBeat") {
     376            0 :         articulation = 7;
     377            0 :     } else if (art == "strongBeat") {
     378            0 :         articulation = 8;
     379              :     } else {
     380            0 :         articulation = 0;
     381              :     }
     382            0 :     e.readNext();
     383            0 : }
     384              : 
     385              : //---------------------------------------------------------
     386              : //   ChordObj::readCapx -- capx equivalent of ChordObj::read
     387              : //---------------------------------------------------------
     388              : 
     389          363 : void ChordObj::readCapx(XmlReader& e)
     390              : {
     391          363 :     stemDir      = StemDir::AUTO;
     392          363 :     dStemLength  = 0;
     393          363 :     nTremoloBars = 0;
     394          363 :     articulation = 0;
     395          363 :     leftTie      = false;
     396          363 :     rightTie     = false;
     397          363 :     beamShift    = 0;
     398          363 :     beamSlope    = 0;
     399          363 :     beamMode      = CapBeamMode::AUTO;
     400          363 :     notationStave = 0;
     401              : 
     402         1122 :     while (e.readNextStartElement()) {
     403          759 :         const AsciiStringView tag(e.name());
     404          759 :         if (tag == "duration") {
     405              :             unsigned int dummy;
     406          363 :             BasicDurationalObj::readCapx(e, dummy);
     407          396 :         } else if (tag == "display") {
     408            0 :             BasicDurationalObj::readCapxDisplay(e);
     409          396 :         } else if (tag == "stem") {
     410            0 :             readCapxStem(e);
     411          396 :         } else if (tag == "beam") {
     412            7 :             LOGD("ChordObj::readCapx: found beam (skipping)");
     413            7 :             e.skipCurrentElement();
     414          389 :         } else if (tag == "articulation") {
     415            0 :             readCapxArticulation(e);
     416          389 :         } else if (tag == "lyric") {
     417            3 :             readCapxLyrics(e);
     418          386 :         } else if (tag == "drawObjects") {
     419           23 :             readCapxObjectArray(e);
     420          363 :         } else if (tag == "heads") {
     421          363 :             readCapxNotes(e);
     422              :         } else {
     423            0 :             e.unknown();
     424              :         }
     425              :     }
     426          363 : }
     427              : 
     428              : //---------------------------------------------------------
     429              : //   pitchStr2Char -- convert pitch string ("[A-G][0-9]") to signed char
     430              : //   notes:
     431              : //   - in .capx middle C is called C5 (which is C4 in MusicXML)
     432              : //   - middle C is MIDI note number 60
     433              : //   - in .cap, pitch contains the diatonic note number relative to clef and key
     434              : //   - as .capx contains absolute notes, store the MIDI note number instead
     435              : //---------------------------------------------------------
     436              : 
     437          363 : static signed char pitchStr2Char(QString& strPitch)
     438              : {
     439          363 :     QRegularExpression pitchRegex(QRegularExpression::anchoredPattern("[A-G][0-9]"));
     440          363 :     QRegularExpressionMatch match = pitchRegex.match(strPitch);
     441              : 
     442          363 :     if (!match.hasMatch()) {
     443            0 :         LOGD("pitchStr2Char: illegal pitch '%s'", qPrintable(strPitch));
     444            0 :         return 0;
     445              :     }
     446              : 
     447          363 :     QString steps("C.D.EF.G.A.B");
     448          363 :     int istep  = steps.indexOf(strPitch.left(1));
     449          363 :     int octave = strPitch.right(1).toInt();
     450          363 :     int pitch  = istep + 12 * octave;
     451              : 
     452          363 :     if (!pitchIsValid(pitch)) {
     453            0 :         pitch = -1;
     454              :     }
     455              : 
     456              :     CAPELLA_TRACE("pitchStr2Char: '%s' -> %d", qPrintable(strPitch), pitch);
     457              : 
     458          363 :     return static_cast<signed char>(pitch);
     459          363 : }
     460              : 
     461              : //---------------------------------------------------------
     462              : //   ChordObj::readCapxLyrics -- read the lyrics in a capx chord
     463              : //---------------------------------------------------------
     464              : 
     465            3 : void ChordObj::readCapxLyrics(XmlReader& e)
     466              : {
     467            9 :     while (e.readNextStartElement()) {
     468            6 :         if (e.name() == "verse") {
     469            6 :             Verse v;
     470            6 :             v.leftAlign = true;              // not used by capella.cpp
     471            6 :             v.extender  = false;             // not used by capella.cpp
     472            6 :             v.hyphen    = e.attribute("hyphen") == "true";
     473            6 :             v.num       = e.intAttribute("i", 0);
     474            6 :             v.verseNumber = e.attribute("verseNumber");       // not used by capella.cpp
     475            6 :             v.text = e.readText();
     476            6 :             verse.append(v);
     477            6 :         } else {
     478            0 :             e.unknown();
     479              :         }
     480              :     }
     481            3 : }
     482              : 
     483              : //---------------------------------------------------------
     484              : //   ChordObj::readCapxNotes -- read the notes in a capx chord
     485              : //---------------------------------------------------------
     486              : 
     487          363 : void ChordObj::readCapxNotes(XmlReader& e)
     488              : {
     489          726 :     while (e.readNextStartElement()) {
     490          363 :         if (e.name() == "head") {
     491          363 :             QString pitch = e.attribute("pitch");
     492          363 :             QString sstep;
     493          363 :             QString shape = e.attribute("shape");
     494          377 :             while (e.readNextStartElement()) {
     495           14 :                 const AsciiStringView tag(e.name());
     496           14 :                 if (tag == "alter") {
     497           12 :                     sstep = e.attribute("step");
     498           12 :                     e.readNext();
     499            2 :                 } else if (tag == "tie") {
     500            2 :                     rightTie = e.attribute("begin") == "true";
     501            2 :                     e.readNext();
     502              :                 } else {
     503            0 :                     e.unknown();
     504              :                 }
     505              :             }
     506              :             CAPELLA_TRACE("ChordObj::readCapxNotes: pitch '%s' altstep '%s' shape '%s'",
     507              :                           qPrintable(pitch), qPrintable(sstep), qPrintable(shape));
     508          363 :             int istep = sstep.toInt();
     509              :             CNote n;
     510          363 :             n.pitch = pitchStr2Char(pitch);
     511          363 :             n.explAlteration = 0;
     512          363 :             n.headType = 0;
     513          363 :             if (shape == "none") {
     514            0 :                 n.headGroup = int(NoteHeadGroup::HEAD_CROSS);
     515              :             } else {
     516          363 :                 n.headGroup = int(NoteHeadGroup::HEAD_NORMAL);
     517              :             }
     518          363 :             n.alteration = istep;
     519          363 :             n.silent = 0;
     520          363 :             notes.append(n);
     521          363 :         } else {
     522            0 :             e.unknown();
     523              :         }
     524              :     }
     525              : 
     526          363 :     if (tupletDenominator <= 0) {
     527          230 :         return; // no tuplet, so we are done
     528              :     }
     529              : 
     530              :     // find a BracketObj in the drawObjects list, get the data and delete it
     531              :     // this is used to read Tuplets with mixed durations
     532          133 :     for (BasicDrawObj* o : objects) {
     533           16 :         if (o->type == CapellaType::BRACKET) {
     534              :             // BracketObj* bo = static_cast<BracketObj*>(o);
     535           16 :             tupletStart = true;
     536           16 :             tupletCount = o->nNotes;
     537           16 :             objects.removeOne(o);
     538           16 :             delete o;
     539              :             CAPELLA_TRACE("==> Tuplet start, plus %d notes till end", tupletCount);
     540           16 :             return; // we are done, and we modified the iterator, so back out
     541              :         }
     542              :     }
     543          117 :     LOGE("no tuplet start found in drawObjects, but tupletDenominator %d", tupletDenominator);
     544              : }
     545              : 
     546              : //---------------------------------------------------------
     547              : //   RestObj::readCapx -- capx equivalent of RestObj::read
     548              : //---------------------------------------------------------
     549              : 
     550            3 : void RestObj::readCapx(XmlReader& e)
     551              : {
     552            3 :     bVerticalCentered = false;
     553            3 :     fullMeasures = 0;
     554            3 :     vertShift    = 0;
     555            8 :     while (e.readNextStartElement()) {
     556            5 :         const AsciiStringView tag(e.name());
     557            5 :         if (tag == "duration") {
     558            3 :             BasicDurationalObj::readCapx(e, fullMeasures);
     559            2 :         } else if (tag == "display") {
     560            0 :             BasicDurationalObj::readCapxDisplay(e);
     561            2 :         } else if (tag == "verticalPos") {
     562            0 :             LOGD("RestObj::readCapx: found verticalPos (skipping)");
     563            0 :             e.skipCurrentElement();
     564            2 :         } else if (tag == "drawObjects") {
     565            2 :             readCapxObjectArray(e);
     566              :         } else {
     567            0 :             e.unknown();
     568              :         }
     569              :     }
     570              : 
     571            3 :     if (tupletDenominator <= 0) {
     572            0 :         return; // no tuplet, so we are done
     573              :     }
     574              : 
     575              :     // find a BracketObj in the drawObjects list, get the data and delete it
     576              :     // this is used to read Tuplets with mixed durations
     577            3 :     for (BasicDrawObj* o : objects) {
     578            2 :         if (o->type == CapellaType::BRACKET) {
     579              :             // BracketObj* bo = static_cast<BracketObj*>(o);
     580            2 :             tupletStart = true;
     581            2 :             tupletCount = o->nNotes;
     582            2 :             objects.removeOne(o);
     583            2 :             delete o;
     584              :             CAPELLA_TRACE("      ==> Tuplet start, plus %d notes till end", tupletCount);
     585            2 :             return; // we are done, and we modified the iterator, so back out
     586              :         }
     587              :     }
     588            1 :     LOGE("no tuplet start found in drawObjects, but tupletDenominator %d", tupletDenominator);
     589              : }
     590              : 
     591              : //---------------------------------------------------------
     592              : //   SimpleTextObj::readCapx -- capx equivalent of SimpleTextObj::read
     593              : //---------------------------------------------------------
     594              : 
     595            6 : void SimpleTextObj::readCapx(XmlReader& e)
     596              : {
     597            6 :     double x = e.doubleAttribute("x");
     598            6 :     double y = e.doubleAttribute("y");
     599            6 :     AsciiStringView stralign = e.asciiAttribute("align", "left");
     600            6 :     align = 0;
     601            6 :     if (stralign == "center") {
     602            6 :         align = 1;
     603            0 :     } else if (stralign == "right") {
     604            0 :         align = 2;
     605              :     }
     606            6 :     relPos = QPointF(x, y);
     607            6 :     relPos *= 32.0;
     608              :     // CAPELLA_TRACE("x %g y %g align %s", x, y, qPrintable(align));
     609           18 :     while (e.readNextStartElement()) {
     610           12 :         const AsciiStringView tag(e.name());
     611           12 :         if (tag == "font") {
     612            6 :             _font = capxReadFont(e);
     613            6 :         } else if (tag == "content") {
     614            6 :             _text = e.readText();
     615              :             // CAPELLA_TRACE("SimpleTextObj::readCapx: found content '%s'", qPrintable(_text));
     616              :         } else {
     617            0 :             e.unknown();
     618              :         }
     619              :     }
     620            6 : }
     621              : 
     622              : //---------------------------------------------------------
     623              : //   TransposableObj::readCapx -- capx equivalent of TransposableObj::read
     624              : //---------------------------------------------------------
     625              : 
     626            0 : void TransposableObj::readCapx(XmlReader& e)
     627              : {
     628            0 :     String enharmonicNote = e.attribute("base");
     629            0 :     while (e.readNextStartElement()) {
     630            0 :         const AsciiStringView tag1(e.name());
     631            0 :         if (tag1 == "drawObj") {
     632            0 :             if (e.attribute("base") == enharmonicNote) {
     633            0 :                 while (e.readNextStartElement()) {
     634            0 :                     const AsciiStringView tag2(e.name());
     635            0 :                     if (tag2 == "group") {
     636            0 :                         variants.append(cap->readCapxDrawObjectArray(e));
     637              :                     }
     638              :                 }
     639              :             } else {
     640            0 :                 e.skipCurrentElement();
     641              :             }
     642              :         }
     643              :     }
     644            0 : }
     645              : 
     646              : //---------------------------------------------------------
     647              : //   SlurObj::readCapx -- capx equivalent of SlurObj::read
     648              : //---------------------------------------------------------
     649              : 
     650            1 : void SlurObj::readCapx(XmlReader& e)
     651              : {
     652              :     // nothing to be done yet
     653            1 :     e.skipCurrentElement();
     654            1 : }
     655              : 
     656              : //---------------------------------------------------------
     657              : //   VoltaObj::readCapx -- capx equivalent of VoltaObj::read
     658              : //---------------------------------------------------------
     659              : 
     660            2 : void VoltaObj::readCapx(XmlReader& e)
     661              : {
     662            2 :     bLeft           = e.asciiAttribute("leftBent", "true") == "true";
     663            2 :     bRight          = e.asciiAttribute("rightBent", "true") == "true";
     664            2 :     bDotted         = e.asciiAttribute("dotted", "false") == "true";
     665            2 :     allNumbers      = e.asciiAttribute("allNumbers", "false") == "true";
     666            2 :     int firstNumber = e.intAttribute("firstNumber", 0);
     667            2 :     int lastNumber  = e.intAttribute("lastNumber", 0);
     668            2 :     if (firstNumber == 0) {
     669              :         // don't know what this means (spec: no number)
     670              :         // cap status equivalent to no first or last number
     671            0 :         from = 1;
     672            0 :         to   = 0;
     673              :     } else {
     674            2 :         from = firstNumber;
     675            2 :         to   = (lastNumber == 0) ? firstNumber : lastNumber;
     676              :     }
     677              :     CAPELLA_TRACE("VoltaObj::read firstNumber %d lastNumber %d", firstNumber, lastNumber);
     678              :     CAPELLA_TRACE("VoltaObj::read x0 %d x1 %d y %d bLeft %d bRight %d bDotted %d allNumbers %d from %d to %d",
     679              :                   x0, x1, y, bLeft, bRight, bDotted, allNumbers, from, to);
     680            2 :     e.readNext();
     681            2 : }
     682              : 
     683              : //---------------------------------------------------------
     684              : //   TrillObj::readCapx -- capx equivalent of TrillObj::read
     685              : //---------------------------------------------------------
     686              : 
     687            0 : void TrillObj::readCapx(XmlReader& e)
     688              : {
     689            0 :     double x0d  = e.doubleAttribute("x1", 0.0);
     690            0 :     double x1d  = e.doubleAttribute("x2", 0.0);
     691            0 :     double yd   = e.doubleAttribute("y", 0.0);
     692            0 :     trillSign   = e.asciiAttribute("tr", "true") == "true";
     693            0 :     x0 = (int)round(x0d * 32.0);
     694            0 :     x1 = (int)round(x1d * 32.0);
     695            0 :     y  = (int)round(yd * 32.0);
     696              :     // color       -> skipped
     697              :     CAPELLA_TRACE("TrillObj::read x0 %d x1 %d y %d trillSign %d", x0, x1, y, trillSign);
     698            0 :     e.readNext();
     699            0 : }
     700              : 
     701              : //---------------------------------------------------------
     702              : //   WedgeObj::readCapx -- capx equivalent of WedgeObj::read
     703              : //---------------------------------------------------------
     704              : 
     705            0 : void WedgeObj::readCapx(XmlReader& e)
     706              : {
     707              :     // TODO: read LineObj properties
     708            0 :     decresc          = e.asciiAttribute("decrescendo", "false") == "true";
     709            0 :     double dheight   = e.doubleAttribute("span", 1.0);
     710            0 :     height = (int)round(dheight * 32.0);
     711            0 :     e.readNext();
     712            0 : }
     713              : 
     714              : //------------------------------------------------------------
     715              : //   BracketObj::readCapx -- capx equivalent of WedgeObj::read
     716              : //------------------------------------------------------------
     717              : 
     718           18 : void BracketObj::readCapx(XmlReader& e)
     719              : {
     720              :     // TODO: We're actually only interested in the <basic> element following and it's noteRange attribute ...
     721              :     // or to put in another way: Intentionally left empty ...
     722           18 :     e.skipCurrentElement();
     723           18 : }
     724              : 
     725              : //---------------------------------------------------------
     726              : //   readCapxDrawObjectArray -- capx equivalent of readDrawObjectArray()
     727              : //---------------------------------------------------------
     728              : 
     729           25 : QList<BasicDrawObj*> Capella::readCapxDrawObjectArray(XmlReader& e)
     730              : {
     731           25 :     QList<BasicDrawObj*> ol;
     732           52 :     while (e.readNextStartElement()) {
     733           27 :         if (e.name() == "drawObj") {
     734           27 :             BasicDrawObj* bdo = 0;
     735           73 :             while (e.readNextStartElement()) {
     736           46 :                 const AsciiStringView tag(e.name());
     737           46 :                 if (tag == "basic") {
     738              :                     // note: the <basic> element always follows the DrawObject it applies to
     739           19 :                     if (bdo) {
     740           19 :                         bdo->readCapx(e);
     741              :                     } else {
     742            0 :                         e.skipCurrentElement();
     743              :                     }
     744           27 :                 } else if (tag == "line") {
     745            0 :                     LOGD("readCapxDrawObjectArray: found line (skipping)");
     746            0 :                     e.skipCurrentElement();
     747           27 :                 } else if (tag == "rectangle") {
     748            0 :                     LOGD("readCapxDrawObjectArray: found rectangle (skipping)");
     749            0 :                     e.skipCurrentElement();
     750           27 :                 } else if (tag == "ellipse") {
     751            0 :                     LOGD("readCapxDrawObjectArray: found ellipse (skipping)");
     752            0 :                     e.skipCurrentElement();
     753           27 :                 } else if (tag == "polygon") {
     754            0 :                     LOGD("readCapxDrawObjectArray: found polygon (skipping)");
     755            0 :                     e.skipCurrentElement();
     756           27 :                 } else if (tag == "metafile") {
     757            0 :                     LOGD("readCapxDrawObjectArray: found metafile (skipping)");
     758            0 :                     e.skipCurrentElement();
     759           27 :                 } else if (tag == "text") {
     760            6 :                     SimpleTextObj* o = new SimpleTextObj(this);
     761            6 :                     bdo = o;           // save o to handle the "basic" tag (which sometimes follows)
     762            6 :                     o->readCapx(e);
     763            6 :                     ol.append(o);
     764           21 :                 } else if (tag == "richText") {
     765            0 :                     LOGD("readCapxDrawObjectArray: found richText (skipping)");
     766            0 :                     e.skipCurrentElement();
     767           21 :                 } else if (tag == "guitar") {
     768            0 :                     LOGD("readCapxDrawObjectArray: found guitar (skipping)");
     769            0 :                     e.skipCurrentElement();
     770           21 :                 } else if (tag == "slur") {
     771            1 :                     SlurObj* o = new SlurObj(this);
     772            1 :                     bdo = o;           // save o to handle the "basic" tag (which sometimes follows)
     773            1 :                     o->readCapx(e);
     774            1 :                     ol.append(o);
     775           20 :                 } else if (tag == "wavyLine") {
     776            0 :                     LOGD("readCapxDrawObjectArray: found wavyLine (skipping)");
     777            0 :                     e.skipCurrentElement();
     778           20 :                 } else if (tag == "bracket") {
     779           18 :                     BracketObj* o = new BracketObj(this);
     780           18 :                     bdo = o;           // save o to handle the "basic" tag (which sometimes follows)
     781           18 :                     o->readCapx(e);
     782           18 :                     ol.append(o);
     783            2 :                 } else if (tag == "wedge") {
     784            0 :                     WedgeObj* o = new WedgeObj(this);
     785            0 :                     bdo = o;           // save o to handle the "basic" tag (which sometimes follows)
     786            0 :                     o->readCapx(e);
     787            0 :                     ol.append(o);
     788            2 :                 } else if (tag == "notelines") {
     789            0 :                     LOGD("readCapxDrawObjectArray: found notelines (skipping)");
     790            0 :                     e.skipCurrentElement();
     791            2 :                 } else if (tag == "volta") {
     792            2 :                     VoltaObj* o = new VoltaObj(this);
     793            2 :                     bdo = o;           // save o to handle the "basic" tag (which sometimes follows)
     794            2 :                     o->readCapx(e);
     795            2 :                     ol.append(o);
     796            0 :                 } else if (tag == "trill") {
     797            0 :                     TrillObj* o = new TrillObj(this);
     798            0 :                     bdo = o;           // save o to handle the "basic" tag (which sometimes follows)
     799            0 :                     o->readCapx(e);
     800            0 :                     ol.append(o);
     801            0 :                 } else if (tag == "transposable") {
     802            0 :                     TransposableObj* o = new TransposableObj(this);
     803            0 :                     bdo = o;           // save o to handle the "basic" tag (which sometimes follows)
     804            0 :                     o->readCapx(e);
     805            0 :                     ol.append(o);
     806            0 :                 } else if (tag == "group") {
     807            0 :                     LOGD("readCapxDrawObjectArray: found group (skipping)");
     808            0 :                     e.skipCurrentElement();
     809              :                 } else {
     810            0 :                     e.unknown();
     811              :                 }
     812              :             }
     813              :         } else {
     814            0 :             e.unknown();
     815              :         }
     816              :     }
     817           25 :     return ol;
     818            0 : }
     819              : 
     820              : //---------------------------------------------------------
     821              : //   readCapxVoice -- capx equivalent of readVoice(CapStaff* cs, int idx)
     822              : //---------------------------------------------------------
     823              : 
     824           43 : void Capella::readCapxVoice(XmlReader& e, CapStaff* cs, int idx)
     825              : {
     826           43 :     CapVoice* v   = new CapVoice;
     827           43 :     v->voiceNo    = idx;
     828           43 :     v->y0Lyrics   = 0;
     829           43 :     v->dyLyrics   = 0;
     830              :     // v->lyricsFont = 0;
     831           43 :     v->stemDir    = 0;
     832              : 
     833           43 :     int tupletCounter = 0;
     834           43 :     Fraction tupletTicks = Fraction(0, 1);
     835           43 :     BasicDurationalObj* tupletObj = nullptr;
     836              : 
     837          102 :     while (e.readNextStartElement()) {
     838           59 :         if (e.name() == "lyricsSettings") {
     839           16 :             LOGD("readCapxVoice: found lyricsSettings (skipping)");
     840           16 :             e.skipCurrentElement();
     841           43 :         } else if (e.name() == "noteObjects") {
     842          479 :             while (e.readNextStartElement()) {
     843          436 :                 const AsciiStringView tag(e.name());
     844          436 :                 if (tag == "clefSign") {
     845           43 :                     CapClef* clef = new CapClef(this);
     846           43 :                     clef->readCapx(e);
     847           43 :                     v->objects.append(clef);
     848          393 :                 } else if (tag == "keySign") {
     849            8 :                     CapKey* key = new CapKey(this);
     850            8 :                     key->readCapx(e);
     851            8 :                     v->objects.append(key);
     852          385 :                 } else if (tag == "timeSign") {
     853           14 :                     CapMeter* meter = new CapMeter(this);
     854           14 :                     meter->readCapx(e);
     855           14 :                     v->objects.append(meter);
     856          371 :                 } else if (tag == "barline") {
     857            5 :                     CapExplicitBarline* bl = new CapExplicitBarline(this);
     858            5 :                     bl->readCapx(e);
     859            5 :                     v->objects.append(bl);
     860          366 :                 } else if (tag == "chord") {
     861          363 :                     ChordObj* chord = new ChordObj(this);
     862          363 :                     chord->readCapx(e);
     863          363 :                     v->objects.append(chord);
     864          363 :                     if (chord->tupletStart) {
     865           16 :                         tupletCounter = chord->tupletCount;
     866           16 :                         tupletTicks   = chord->ticks();
     867           16 :                         tupletObj     = chord;  // save the tuplet object for later
     868              :                         CAPELLA_TRACE("     ==| (C) start of tuplet with %d notes, ticks %d",
     869              :                                       1 + tupletCounter, tupletTicks.ticks());
     870          347 :                     } else if (tupletCounter > 0) {
     871          113 :                         if (!tupletObj) {
     872            0 :                             LOGE("     !!! chord with tupletCounter %d without tupletObj", tupletCounter);
     873              :                         } else {
     874          113 :                             tupletTicks += chord->ticks();
     875          113 :                             if (chord->tupletDenominator == 0) {
     876            1 :                                 LOGE("     !!! (C) chord with tupletCounter %d outside tuplet", tupletCounter);
     877            1 :                                 chord->tupletDenominator = tupletObj->tupletDenominator;  // copy denominator from the tuplet object
     878              :                             }
     879          113 :                             --tupletCounter;
     880          113 :                             if (tupletCounter == 0) {
     881           18 :                                 chord->tupletEnd = true;
     882           18 :                                 tupletObj->tupletTicks = tupletTicks;      // set total ticks to the tuplet object
     883           18 :                                 tupletObj = nullptr;      // reset tuplet object
     884              :                                 CAPELLA_TRACE("     ==| end of tuplet, total ticks %d", tupletTicks.ticks());
     885              :                             } else {
     886              :                                 CAPELLA_TRACE("     ==> chord with tupletCounter %d inside tuplet, ticks now %d",
     887              :                                               tupletCounter, tupletTicks.ticks());
     888              :                             }
     889              :                         }
     890              :                     }
     891            3 :                 } else if (tag == "rest") {
     892            3 :                     RestObj* rest = new RestObj(this);
     893            3 :                     rest->readCapx(e);
     894            3 :                     v->objects.append(rest);
     895            3 :                     if (rest->tupletStart) {
     896            2 :                         tupletCounter = rest->tupletCount;
     897            2 :                         tupletTicks   = rest->ticks();
     898            2 :                         tupletObj     = rest;  // save the tuplet object for later
     899              :                         CAPELLA_TRACE("     ==| (R) start of tuplet with %d notes, ticks %d",
     900              :                                       1 + tupletCounter, tupletTicks.ticks());
     901            1 :                     } else if (tupletCounter > 0) {
     902            0 :                         if (!tupletObj) {
     903            0 :                             LOGE("     !!! rest with tupletCounter %d without tupletObj", tupletCounter);
     904              :                         } else {
     905              :                             // add the rest ticks to the tuplet ticks
     906            0 :                             tupletTicks += rest->ticks();
     907            0 :                             if (rest->tupletDenominator == 0) {
     908            0 :                                 LOGE("     !!! rest with tupletCounter %d outside tuplet", tupletCounter);
     909            0 :                                 rest->tupletDenominator = tupletObj->tupletDenominator;  // copy denominator from the tuplet object
     910              :                             }
     911            0 :                             --tupletCounter;
     912            0 :                             if (tupletCounter == 0) {
     913            0 :                                 rest->tupletEnd = true;
     914            0 :                                 tupletObj->tupletTicks = tupletTicks;      // set total ticks to the tuplet object
     915            0 :                                 tupletObj = nullptr;      // reset tuplet object
     916              :                                 CAPELLA_TRACE("     ==| end of tuplet, total ticks %d", tupletTicks.ticks());
     917              :                             } else {
     918              :                                 CAPELLA_TRACE("     ==> rest with tupletCounter %d inside tuplet, ticks now %d",
     919              :                                               tupletCounter, tupletTicks.ticks());
     920              :                             }
     921              :                         }
     922              :                     }
     923              :                 } else {
     924            0 :                     e.unknown();
     925              :                 }
     926              :             }
     927              :         } else {
     928            0 :             e.unknown();
     929              :         }
     930              :     }
     931              : 
     932           43 :     cs->voices.append(v);
     933           43 : }
     934              : 
     935              : //---------------------------------------------------------
     936              : //   findStaffIndex -- find index of layout in staffLayouts
     937              : //---------------------------------------------------------
     938              : 
     939           43 : static int findStaffIndex(const QString& layout, const QList<CapStaffLayout*>& staffLayouts)
     940              : {
     941           43 :     int index = 0;
     942           49 :     for (const auto& capStaffLayout : staffLayouts) {
     943           49 :         if (capStaffLayout->descr == layout) {
     944           43 :             return index;
     945              :         }
     946            6 :         ++index;
     947              :     }
     948              : 
     949            0 :     ASSERT_X(QString::asprintf("staff layout '%s' not found", qPrintable(layout)));
     950              :     return 0;
     951              : }
     952              : 
     953              : //---------------------------------------------------------
     954              : //   readCapxStaff -- capx equivalent of readStaff(CapSystem* system)
     955              : //---------------------------------------------------------
     956              : 
     957           43 : void Capella::readCapxStaff(XmlReader& e, CapSystem* system)
     958              : {
     959              :     CAPELLA_TRACE("Capella::readCapxStaff");
     960           43 :     CapStaff* staff = new CapStaff;
     961           43 :     QString layout = e.attribute("layout");
     962           43 :     QString time = e.attribute("defaultTime");
     963           43 :     qstring2timesig(time, staff->numerator, staff->log2Denom, staff->allaBreve);
     964              : 
     965           43 :     staff->iLayout   = findStaffIndex(layout, staffLayouts());
     966           43 :     staff->topDistX  = 0;
     967           43 :     staff->btmDistX  = 0;
     968           43 :     staff->color     = Qt::black;
     969              : 
     970           88 :     while (e.readNextStartElement()) {
     971           45 :         const AsciiStringView tag(e.name());
     972           45 :         if (tag == "extraDistance") {
     973            2 :             LOGD("readCapxStaff: found extraDistance (skipping)");
     974            2 :             e.skipCurrentElement();
     975           43 :         } else if (tag == "voices") {
     976           43 :             int i = 0;
     977           86 :             while (e.readNextStartElement()) {
     978           43 :                 if (e.name() == "voice") {
     979           43 :                     readCapxVoice(e, staff, i);
     980           43 :                     ++i;
     981              :                 } else {
     982            0 :                     e.unknown();
     983              :                 }
     984              :             }
     985              :         } else {
     986            0 :             e.unknown();
     987              :         }
     988              :     }
     989              : 
     990           43 :     system->staves[staff->iLayout] = staff;
     991           43 : }
     992              : 
     993              : //---------------------------------------------------------
     994              : //   initUnreadStaves
     995              : //---------------------------------------------------------
     996              : 
     997           39 : static void initUnreadStaves(QList<CapStaff*>& staves)
     998              : {
     999              :     // find the first staff actually read
    1000           39 :     CapStaff* firstNonEmptyStaff = nullptr;
    1001           84 :     for (const auto staff : staves) {
    1002           45 :         if (staff && !firstNonEmptyStaff) {
    1003           39 :             firstNonEmptyStaff = staff;
    1004              :         }
    1005              :     }
    1006              : 
    1007              :     // copy its data to the staves present in the file
    1008           84 :     for (int i = 0; i < staves.size(); ++i) {
    1009           45 :         if (!(staves.at(i))) {
    1010            2 :             auto staff = new CapStaff;
    1011            2 :             staff->numerator = firstNonEmptyStaff->numerator;
    1012            2 :             staff->log2Denom = firstNonEmptyStaff->log2Denom;
    1013            2 :             staff->allaBreve = firstNonEmptyStaff->allaBreve;
    1014            2 :             staff->iLayout = i;
    1015            2 :             staves[i] = staff;
    1016              :         }
    1017              :     }
    1018           39 : }
    1019              : 
    1020              : //---------------------------------------------------------
    1021              : //   readCapxSystem -- capx equivalent of readSystem()
    1022              : //---------------------------------------------------------
    1023              : 
    1024           39 : void Capella::readCapxSystem(XmlReader& e)
    1025              : {
    1026           39 :     CapSystem* s = new CapSystem;
    1027           39 :     s->nAddBarCount   = 0;
    1028           39 :     s->bBarCountReset = 0;
    1029           39 :     s->explLeftIndent = 0;   // ?? TODO ?? use in capella.cpp commented out
    1030              : 
    1031           39 :     s->beamMode = CapBeamMode::AUTO;
    1032           39 :     s->tempo    = 0;
    1033           39 :     s->color    = Qt::black;
    1034              : 
    1035           39 :     unsigned char b  = 0;
    1036           39 :     s->bJustified    = b & 2;
    1037           39 :     s->bPageBreak    = (b & 4) != 0;
    1038           39 :     s->instrNotation = (b >> 3) & 7;
    1039              : 
    1040              :     // set all staves in this system to "not yet read"
    1041           84 :     for (int i = 0; i < _staffLayouts.size(); ++i) {
    1042           45 :         s->staves.append(nullptr);
    1043              :     }
    1044              : 
    1045              :     // read staves
    1046           78 :     while (e.readNextStartElement()) {
    1047           39 :         const AsciiStringView tag(e.name());
    1048           39 :         if (tag == "barCount") {
    1049            0 :             LOGD("readCapxSystem: found barCount (skipping)");
    1050            0 :             e.skipCurrentElement();
    1051           39 :         } else if (tag == "staves") {
    1052           82 :             while (e.readNextStartElement()) {
    1053           43 :                 if (e.name() == "staff") {
    1054           43 :                     readCapxStaff(e, s);
    1055              :                 } else {
    1056            0 :                     e.unknown();
    1057              :                 }
    1058              :             }
    1059              :         } else {
    1060            0 :             e.unknown();
    1061              :         }
    1062              :     }
    1063              : 
    1064              :     // initializes staves not read
    1065           39 :     initUnreadStaves(s->staves);
    1066              : 
    1067           39 :     systems.append(s);
    1068           39 : }
    1069              : 
    1070              : //---------------------------------------------------------
    1071              : //   capxSystems -- read the capx <systems> element
    1072              : //---------------------------------------------------------
    1073              : 
    1074           18 : void Capella::capxSystems(XmlReader& e)
    1075              : {
    1076           57 :     while (e.readNextStartElement()) {
    1077           39 :         const AsciiStringView tag(e.name());
    1078           39 :         if (tag == "system") {
    1079           39 :             readCapxSystem(e);
    1080              :         } else {
    1081            0 :             e.unknown();
    1082              :         }
    1083              :     }
    1084           18 : }
    1085              : 
    1086              : //---------------------------------------------------------
    1087              : //   capxNotation -- read the capx <notation> element
    1088              : //---------------------------------------------------------
    1089              : 
    1090            3 : static void capxNotation(XmlReader& e, uchar& barlineMode, uchar& barlineFrom, uchar& barlineTo)
    1091              : {
    1092            6 :     while (e.readNextStartElement()) {
    1093            3 :         if (e.name() == "barlines") {
    1094            3 :             AsciiStringView mode = e.asciiAttribute("mode", "full");
    1095            3 :             if (mode == "full") {
    1096            0 :                 barlineMode = 3;
    1097            3 :             } else if (mode == "none") {
    1098            0 :                 barlineMode = 2;                                // correct ? not handled by capella.cpp anyway
    1099            3 :             } else if (mode == "internal") {
    1100            3 :                 barlineMode = 1;
    1101            0 :             } else if (mode == "external") {
    1102            0 :                 barlineMode = 0;                                // correct ? not handled by capella.cpp anyway
    1103              :             } else {
    1104            0 :                 barlineMode = 1;
    1105              :             }
    1106            3 :             barlineFrom = e.intAttribute("from");
    1107            3 :             barlineTo   = e.intAttribute("to");
    1108            3 :             e.readNext();
    1109              :         } else {
    1110            0 :             e.unknown();
    1111              :         }
    1112              :     }
    1113            3 : }
    1114              : 
    1115              : //---------------------------------------------------------
    1116              : //   readCapxStaveLayout -- capx equivalent of readStaveLayout(CapStaffLayout*, int)
    1117              : //   read the staffLayout element
    1118              : //---------------------------------------------------------
    1119              : 
    1120           21 : void Capella::readCapxStaveLayout(XmlReader& e, CapStaffLayout* sl, int /*idx*/)
    1121              : {
    1122              :     // initialize same variables as readStaveLayout(CapStaffLayout*, int)
    1123              : 
    1124           21 :     sl->barlineMode = 0;
    1125           21 :     sl->noteLines   = 0;
    1126              : 
    1127           21 :     sl->bSmall      = 0;   // TODO (unclear where this is in the capx file)
    1128              : 
    1129           21 :     sl->topDist      = 0;
    1130           21 :     sl->btmDist      = 0;
    1131           21 :     sl->groupDist    = 0;
    1132           21 :     sl->barlineFrom = 0;
    1133           21 :     sl->barlineTo   = 0;
    1134              : 
    1135           21 :     unsigned char clef = 0;
    1136           21 :     sl->form = Form(clef & 7);
    1137           21 :     sl->line = ClefLine((clef >> 3) & 7);
    1138           21 :     sl->oct  = Oct((clef >> 6));
    1139              :     // CAPELLA_TRACE("   clef %x  form %d, line %d, oct %d", clef, sl->form, sl->line, sl->oct);
    1140              : 
    1141              :     // Schlagzeuginformation
    1142           21 :     unsigned char b   = 0;   // ?? TODO ?? sl->soundMapIn and sl->soundMapOut are not used
    1143           21 :     sl->bPercussion  = b & 1;      // Schlagzeugkanal verwenden
    1144           21 :     sl->bSoundMapIn  = b & 2;
    1145           21 :     sl->bSoundMapOut = b & 4;
    1146              :     /*
    1147              :     if (sl->bSoundMapIn) {      // Umleitungstabelle fr Eingabe vom Keyboard
    1148              :           uchar iMin = readByte();
    1149              :           Q_UNUSED(iMin);
    1150              :           uchar n    = readByte();
    1151              :           Q_ASSERT(n > 0 and iMin + n <= 128);
    1152              :           f->read(sl->soundMapIn, n);
    1153              :           curPos += n;
    1154              :           }
    1155              :     if (sl->bSoundMapOut) {     // Umleitungstabelle fr das Vorspielen
    1156              :           unsigned char iMin = readByte();
    1157              :           unsigned char n    = readByte();
    1158              :           Q_ASSERT(n > 0 and iMin + n <= 128);
    1159              :           f->read(sl->soundMapOut, n);
    1160              :           curPos += n;
    1161              :           }
    1162              :     */
    1163           21 :     sl->sound  = 0;
    1164           21 :     sl->volume = 0;
    1165           21 :     sl->transp = 0;
    1166              : 
    1167              :     CAPELLA_TRACE("readCapxStaveLayout");
    1168           21 :     sl->descr = e.attribute("description");
    1169           52 :     while (e.readNextStartElement()) {
    1170           31 :         const AsciiStringView tag(e.name());
    1171           31 :         if (tag == "notation") {
    1172            3 :             capxNotation(e, sl->barlineMode, sl->barlineFrom, sl->barlineTo);
    1173           28 :         } else if (tag == "distances") {
    1174            3 :             LOGD("readCapxStaveLayout: found distances (skipping)");
    1175            3 :             e.skipCurrentElement();
    1176           25 :         } else if (tag == "instrument") {
    1177            4 :             sl->name = e.attribute("name");
    1178            4 :             sl->abbrev = e.attribute("abbrev");
    1179              :             // elements name and abbrev overrule attributes name and abbrev
    1180           10 :             while (e.readNextStartElement()) {
    1181            6 :                 const AsciiStringView t(e.name());
    1182            6 :                 if (t == "name") {
    1183            3 :                     sl->name = e.readText();
    1184            3 :                 } else if (t == "abbrev") {
    1185            3 :                     sl->abbrev = e.readText();
    1186              :                 } else {
    1187            0 :                     e.unknown();
    1188              :                 }
    1189              :             }
    1190           21 :         } else if (tag == "sound") {
    1191           21 :             sl->sound = e.intAttribute("instr", 0);
    1192           21 :             sl->volume = e.intAttribute("volume", 0);
    1193           21 :             sl->transp = e.intAttribute("transpose", 0);
    1194           21 :             e.readNext();
    1195              :         } else {
    1196            0 :             e.unknown();
    1197              :         }
    1198              :     }
    1199              :     CAPELLA_TRACE("   descr '%s' name '%s' abbrev '%s'",
    1200              :                   qPrintable(sl->descr), qPrintable(sl->name), qPrintable(sl->abbrev));
    1201              :     CAPELLA_TRACE("   sound %d vol %d transp %d", sl->sound, sl->volume, sl->transp);
    1202              :     CAPELLA_TRACE("readCapxStaveLayout done");
    1203           21 : }
    1204              : 
    1205              : //---------------------------------------------------------
    1206              : //   capxLayoutBrackets -- read the capx <brackets> element (when child of <layout)
    1207              : //---------------------------------------------------------
    1208              : 
    1209            1 : static void capxLayoutBrackets(XmlReader& e, QList<CapBracket>& bracketList)
    1210              : {
    1211            2 :     while (e.readNextStartElement()) {
    1212            1 :         if (e.name() == "bracket") {
    1213              :             CapBracket b;
    1214            1 :             b.from  = e.intAttribute("from");
    1215            1 :             b.to    = e.intAttribute("to");
    1216            1 :             b.curly = e.attribute("curly") == "true";
    1217              :             // LOGD("Bracket%d %d-%d curly %d", i, b.from, b.to, b.curly);
    1218            1 :             bracketList.append(b);
    1219            1 :             e.readNext();
    1220              :         } else {
    1221            0 :             e.unknown();
    1222              :         }
    1223              :     }
    1224            1 : }
    1225              : 
    1226              : //---------------------------------------------------------
    1227              : //   capxLayoutDistances -- read the capx <Distances> element (when child of <layout)
    1228              : //---------------------------------------------------------
    1229              : 
    1230            3 : static void capxLayoutDistances(XmlReader& e, double& smallLineDist, double& normalLineDist, int& topDist)
    1231              : {
    1232            9 :     while (e.readNextStartElement()) {
    1233            6 :         const AsciiStringView tag(e.name());
    1234            6 :         if (tag == "staffLines") {
    1235            3 :             smallLineDist = e.doubleAttribute("small");
    1236            3 :             normalLineDist = e.doubleAttribute("normal");
    1237            3 :             e.readNext();
    1238            3 :         } else if (tag == "systems") {
    1239            3 :             topDist = e.intAttribute("top");
    1240            3 :             e.readNext();
    1241              :         } else {
    1242            0 :             e.unknown();
    1243              :         }
    1244              :     }
    1245            3 : }
    1246              : 
    1247              : //---------------------------------------------------------
    1248              : //   capxLayoutStaves -- read the capx <staves> element (when child of <layout)
    1249              : //---------------------------------------------------------
    1250              : 
    1251           18 : void Capella::capxLayoutStaves(XmlReader& e)
    1252              : {
    1253           18 :     int iStave = 0;
    1254           39 :     while (e.readNextStartElement()) {
    1255           21 :         if (e.name() == "staffLayout") {
    1256           21 :             CapStaffLayout* sl = new CapStaffLayout;
    1257           21 :             readCapxStaveLayout(e, sl, iStave);
    1258           21 :             _staffLayouts.append(sl);
    1259           21 :             ++iStave;
    1260              :         } else {
    1261            0 :             e.unknown();
    1262              :         }
    1263              :     }
    1264           18 : }
    1265              : 
    1266              : //---------------------------------------------------------
    1267              : //   capxLayout -- read the capx <layout> element
    1268              : //   rough equivalent of readLayout() read part
    1269              : //---------------------------------------------------------
    1270              : 
    1271           18 : void Capella::capxLayout(XmlReader& e)
    1272              : {
    1273           53 :     while (e.readNextStartElement()) {
    1274           35 :         const AsciiStringView tag(e.name());
    1275           35 :         if (tag == "pages") {
    1276            3 :             LOGD("capxLayout: found pages (skipping)");
    1277            3 :             e.skipCurrentElement();
    1278           32 :         } else if (tag == "distances") {
    1279            3 :             capxLayoutDistances(e, smallLineDist, normalLineDist, topDist);
    1280              :             // LOGD("Capella::capxLayout(): smallLineDist %g normalLineDist %g topDist %d",
    1281              :             //        smallLineDist, normalLineDist, topDist);
    1282           29 :         } else if (tag == "instrumentNames") {
    1283            3 :             LOGD("capxLayout: found instrumentNames (skipping)");
    1284            3 :             e.skipCurrentElement();
    1285           26 :         } else if (tag == "style") {
    1286            1 :             LOGD("capxLayout: found style (skipping)");
    1287            1 :             e.skipCurrentElement();
    1288           25 :         } else if (tag == "staves") {
    1289           18 :             capxLayoutStaves(e);
    1290            7 :         } else if (tag == "brackets") {
    1291            1 :             capxLayoutBrackets(e, brackets);
    1292            6 :         } else if (tag == "spacing") {
    1293            3 :             LOGD("capxLayout: found spacing (skipping)");
    1294            3 :             e.skipCurrentElement();
    1295            3 :         } else if (tag == "beamFlattening") {
    1296            3 :             LOGD("capxLayout: found beamFlattening (skipping)");
    1297            3 :             e.skipCurrentElement();
    1298              :         } else {
    1299            0 :             e.unknown();
    1300              :         }
    1301              :     }
    1302           18 : }
    1303              : 
    1304              : //---------------------------------------------------------
    1305              : //   initCapxLayout -- capx equivalent of readLayout() initialize part
    1306              : //---------------------------------------------------------
    1307              : 
    1308           18 : void Capella::initCapxLayout()
    1309              : {
    1310              :     // initialize same variables as readLayout()
    1311              : 
    1312           18 :     smallLineDist  = 1.2;    // TODO verify default
    1313           18 :     normalLineDist = 1.76;   // TODO verify default
    1314              : 
    1315           18 :     topDist        = 14;     // TODO verify default
    1316           18 :     interDist      = 0;      // TODO verify default
    1317              : 
    1318           18 :     txtAlign   = 0;
    1319           18 :     adjustVert = 0;
    1320              : 
    1321           18 :     unsigned char b          = 0;
    1322           18 :     redundantKeys    = b & 1;
    1323           18 :     modernDoubleNote = b & 2;
    1324           18 :     Q_ASSERT((b & 0xFC) == 0);   // bits 2...7 reserviert
    1325              : 
    1326           18 :     bSystemSeparators = 0;
    1327           18 :     nUnnamed           = 0;
    1328              : 
    1329              :     // namesFont = ... // ?? not used ??
    1330              : 
    1331              :     // Note: readLayout() also reads stave layout (using readStaveLayout(sl, iStave))
    1332              :     // and system brackets. Here this is handled by readCapx(XmlReader& e).
    1333           18 : }
    1334              : 
    1335              : //---------------------------------------------------------
    1336              : //   readCapx -- capx equivalent of read(QFile* fp)
    1337              : //---------------------------------------------------------
    1338              : 
    1339           18 : void Capella::readCapx(XmlReader& e)
    1340              : {
    1341              :     // initialize same variables as read(QFile* fp)
    1342              : 
    1343           18 :     f      = 0;
    1344           18 :     curPos = 0;
    1345              : 
    1346           18 :     author   = 0;
    1347           18 :     keywords = 0;
    1348           18 :     comment  = 0;
    1349              : 
    1350           18 :     nRel   = 0;
    1351           18 :     nAbs   = 0;
    1352           18 :     unsigned char b   = 0;
    1353           18 :     bUseRealSize      = b & 1;
    1354           18 :     bAllowCompression = b & 2;
    1355           18 :     bPrintLandscape   = b & 16;
    1356              : 
    1357           18 :     beamRelMin0 = 0;
    1358           18 :     beamRelMin1 = 0;
    1359           18 :     beamRelMax0 = 0;
    1360           18 :     beamRelMax1 = 0;
    1361              : 
    1362           18 :     backgroundChord = new ChordObj(this);           // contains graphic objects on the page background
    1363           18 :     bShowBarCount    = 0;
    1364           18 :     barNumberFrame   = 0;
    1365           18 :     nBarDistX        = 0;
    1366           18 :     nBarDistY        = 0;
    1367              :     // QFont barNumFont = ... // not used ?
    1368           18 :     nFirstPage       = 0;
    1369           18 :     leftPageMargins  = 0;
    1370           18 :     topPageMargins   = 0;
    1371           18 :     rightPageMargins = 0;
    1372           18 :     btmPageMargins   = 0;
    1373              : 
    1374              :     // Now do the equivalent of:
    1375              :     // readLayout(); (called only once)
    1376              :     // readExtra();  (this is a NOP)
    1377              :     // readDrawObjectArray();
    1378              :     // for (unsigned i = 0; i < nSystems; i++)
    1379              :     //       readSystem();
    1380              : 
    1381           18 :     initCapxLayout();
    1382              : 
    1383              :     // read stave layout
    1384              :     // read systems
    1385           58 :     while (e.readNextStartElement()) {
    1386           40 :         const AsciiStringView tag(e.name());
    1387           40 :         if (tag == "info") {
    1388            3 :             LOGD("importCapXml: found info (skipping)");
    1389            3 :             e.skipCurrentElement();
    1390           37 :         } else if (tag == "layout") {
    1391           18 :             capxLayout(e);
    1392           19 :         } else if (tag == "gallery") {
    1393            0 :             LOGD("capxLayout: found gallery (skipping)");
    1394            0 :             e.skipCurrentElement();
    1395           19 :         } else if (tag == "pageObjects") {
    1396            0 :             backgroundChord->readCapxObjectArray(e);
    1397           19 :         } else if (tag == "barCount") {
    1398            1 :             LOGD("importCapXml: found barCount (skipping)");
    1399            1 :             e.skipCurrentElement();
    1400           18 :         } else if (tag == "systems") {
    1401           18 :             capxSystems(e);
    1402              :         } else {
    1403            0 :             e.unknown();
    1404              :         }
    1405              :     }
    1406           18 : }
    1407              : 
    1408              : //---------------------------------------------------------
    1409              : //   importCapXml
    1410              : //---------------------------------------------------------
    1411              : 
    1412              : void convertCapella(Score* score, Capella* cap, bool capxMode);
    1413              : 
    1414           18 : Err importCapXml(MasterScore* score, const QString& name)
    1415              : {
    1416           18 :     LOGD("importCapXml(score %p, name %s)", score, qPrintable(name));
    1417           18 :     muse::ZipReader uz(name);
    1418           18 :     if (!uz.exists()) {
    1419            0 :         LOGD("importCapXml: <%s> not found", qPrintable(name));
    1420            0 :         return Err::FileNotFound;
    1421              :     }
    1422              : 
    1423           18 :     muse::ByteArray dbuf = uz.fileData("score.xml");
    1424           18 :     XmlReader e(dbuf);
    1425           18 :     e.setDocName(name);
    1426           18 :     Capella cf;
    1427              : 
    1428           36 :     while (e.readNextStartElement()) {
    1429           18 :         if (e.name() == "score") {
    1430           18 :             String xmlns = e.attribute("xmlns", u"<none>");       // doesn't work ???
    1431              :             CAPELLA_TRACE("importCapXml: found score, namespace '%s'", xmlns.toUtf8().constChar());
    1432           18 :             cf.readCapx(e);
    1433           18 :         } else {
    1434            0 :             e.unknown();
    1435              :         }
    1436              :     }
    1437              : 
    1438              :     try {
    1439           18 :         convertCapella(score, &cf, true);
    1440              :     }
    1441            0 :     catch (Capella::Error) {
    1442              :         // avoid another error message box
    1443            0 :         return Err::NoError;
    1444            0 :     }
    1445           18 :     return Err::NoError;
    1446           18 : }
    1447              : }
        

Generated by: LCOV version 2.0-1