    //=============================================================================
    //  MuseScore
    //  Linux Music Score Editor
    //
    //  Copyright (C) 2015 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.
    //
    //  This program is distributed in the hope that it will be useful,
    //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    //  GNU General Public License for more details.
    //
    //  You should have received a copy of the GNU General Public License
    //  along with this program; if not, write to the Free Software
    //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    //=============================================================================

    #include "libmscore/box.h"
    #include "libmscore/instrtemplate.h"
    #include "libmscore/measure.h"
    #include "libmscore/page.h"
    #include "libmscore/part.h"
    #include "libmscore/staff.h"
    #include "libmscore/stringdata.h"
    #include "libmscore/sym.h"
    #include "libmscore/symbol.h"
    #include "libmscore/timesig.h"
    #include "libmscore/style.h"
    #include "libmscore/spanner.h"
    #include "libmscore/bracketItem.h"

    #include "importmxmlpass1.h"
    #include "importmxmlpass2.h"
    #include "preferences.h"

    namespace Ms {

    //---------------------------------------------------------
    //   noteTypeToFraction
    //---------------------------------------------------------

    /**
     Convert MusicXML note type to fraction.
     */

    static Fraction noteTypeToFraction(const QString& type)
          {
          if (type == "1024th")
                return Fraction(1, 1024);
          else if (type == "512th")
                return Fraction(1, 512);
          else if (type == "256th")
                return Fraction(1, 256);
          else if (type == "128th")
                return Fraction(1, 128);
          else if (type == "64th")
                return Fraction(1, 64);
          else if (type == "32nd")
                return Fraction(1, 32);
          else if (type == "16th")
                return Fraction(1, 16);
          else if (type == "eighth")
                return Fraction(1, 8);
          else if (type == "quarter")
                return Fraction(1, 4);
          else if (type == "half")
                return Fraction(1, 2);
          else if (type == "whole")
                return Fraction(1, 1);
          else if (type == "breve")
                return Fraction(2, 1);
          else if (type == "long")
                return Fraction(4, 1);
          else if (type == "maxima")
                return Fraction(8, 1);
          else
                return Fraction(0, 0);
          }

    //---------------------------------------------------------
    //   calculateFraction
    //---------------------------------------------------------

    /**
     Convert note type, number of dots and actual and normal notes into a duration
     */

    static Fraction calculateFraction(const QString& type, const int dots, const Fraction timeMod)
          {
          // type
          Fraction f = noteTypeToFraction(type);
          if (f.isValid()) {
                // dot(s)
                Fraction f_no_dots = f;
                for (int i = 0; i < dots; ++i)
                      f += (f_no_dots / (2 << i));
                // tuplet
                if (timeMod.isValid())
                      f *= timeMod;
                // clean up (just in case)
                f.reduce();
                }
          return f;
          }

    //---------------------------------------------------------
    //   allocateStaves
    //---------------------------------------------------------

    /**
     Allocate MuseScore staff to MusicXML voices.
     For each staff, allocate at most VOICES voices to the staff.
     */

    // for regular (non-overlapping) voices:
    // 1) assign voice to a staff (allocateStaves)
    // 2) assign voice numbers (allocateVoices)
    // due to cross-staving, it is not a priori clear to which staff
    // a voice has to be assigned
    // allocate ordered by number of chordrests in the MusicXML voice
    //
    // for overlapping voices:
    // 1) assign voice to staves it is found in (allocateStaves)
    // 2) assign voice numbers (allocateVoices)

    static void allocateStaves(VoiceList& vcLst)
          {
          // initialize
          int voicesAllocated[MAX_STAVES]; // number of voices allocated on each staff
          for (int i = 0; i < MAX_STAVES; ++i)
                voicesAllocated[i] = 0;

          // handle regular (non-overlapping) voices
          // note: outer loop executed vcLst.size() times, as each inner loop handles exactly one item
          for (int i = 0; i < vcLst.size(); ++i) {
                // find the regular voice containing the highest number of chords and rests that has not been handled yet
                int max = 0;
                QString key;
                for (VoiceList::const_iterator j = vcLst.constBegin(); j != vcLst.constEnd(); ++j) {
                      if (!j.value().overlaps() && j.value().numberChordRests() > max && j.value().staff() == -1) {
                            max = j.value().numberChordRests();
                            key = j.key();
                            }
                      }
                if (key != "") {
                      int prefSt = vcLst.value(key).preferredStaff();
                      if (voicesAllocated[prefSt] < VOICES) {
                            vcLst[key].setStaff(prefSt);
                            voicesAllocated[prefSt]++;
                            }
                      else
                            // out of voices: mark as used but not allocated
                            vcLst[key].setStaff(-2);
                      }
                }

          // handle overlapping voices
          // for every staff allocate remaining voices (if space allows)
          // the ones with the highest number of chords and rests get allocated first
          for (int h = 0; h < MAX_STAVES; ++h) {
                // note: middle loop executed vcLst.size() times, as each inner loop handles exactly one item
                for (int i = 0; i < vcLst.size(); ++i) {
                      // find the overlapping voice containing the highest number of chords and rests that has not been handled yet
                      int max = 0;
                      QString key;
                      for (VoiceList::const_iterator j = vcLst.constBegin(); j != vcLst.constEnd(); ++j) {
                            if (j.value().overlaps() && j.value().numberChordRests(h) > max && j.value().staffAlloc(h) == -1) {
                                  max = j.value().numberChordRests(h);
                                  key = j.key();
                                  }
                            }
                      if (key != "") {
                            int prefSt = h;
                            if (voicesAllocated[prefSt] < VOICES) {
                                  vcLst[key].setStaffAlloc(prefSt, 1);
                                  voicesAllocated[prefSt]++;
                                  }
                            else
                                  // out of voices: mark as used but not allocated
                                  vcLst[key].setStaffAlloc(prefSt, -2);
                            }
                      }
                }
          }

    //---------------------------------------------------------
    //   allocateVoices
    //---------------------------------------------------------

    /**
     Allocate MuseScore voice to MusicXML voices.
     For each staff, the voices are number 1, 2, 3, 4
     in the same order they are numbered in the MusicXML file.
     */

    static void allocateVoices(VoiceList& vcLst)
          {
          int nextVoice[MAX_STAVES]; // number of voices allocated on each staff
          for (int i = 0; i < MAX_STAVES; ++i)
                nextVoice[i] = 0;
          // handle regular (non-overlapping) voices
          // a voice is allocated on one specific staff
          for (VoiceList::const_iterator i = vcLst.constBegin(); i != vcLst.constEnd(); ++i) {
                int staff = i.value().staff();
                QString key   = i.key();
                if (staff >= 0) {
                      vcLst[key].setVoice(nextVoice[staff]);
                      nextVoice[staff]++;
                      }
                }
          // handle overlapping voices
          // each voice may be in every staff
          for (VoiceList::const_iterator i = vcLst.constBegin(); i != vcLst.constEnd(); ++i) {
                for (int j = 0; j < MAX_STAVES; ++j) {
                      int staffAlloc = i.value().staffAlloc(j);
                      QString key   = i.key();
                      if (staffAlloc >= 0) {
                            vcLst[key].setVoice(j, nextVoice[j]);
                            nextVoice[j]++;
                            }
                      }
                }
          }

    //---------------------------------------------------------
    //   copyOverlapData
    //---------------------------------------------------------

    /**
     Copy the overlap data from the overlap detector to the voice list.
     */

    static void copyOverlapData(VoiceOverlapDetector& vod, VoiceList& vcLst)
          {
          for (VoiceList::const_iterator i = vcLst.constBegin(); i != vcLst.constEnd(); ++i) {
                QString key = i.key();
                if (vod.stavesOverlap(key))
                      vcLst[key].setOverlap(true);
                }
          }

    //---------------------------------------------------------
    //   MusicXMLParserPass1
    //---------------------------------------------------------

    MusicXMLParserPass1::MusicXMLParserPass1(Score* score)
          : _divs(0), _score(score)
          {
          // nothing
          }

    //---------------------------------------------------------
    //   initPartState
    //---------------------------------------------------------

    /**
     Initialize members as required for reading the MusicXML part element.
     TODO: factor out part reading into a separate class
     TODO: preferably use automatically initialized variables
     Note that Qt automatically initializes new elements in QVector (tuplets).
     */

    void MusicXMLParserPass1::initPartState(const QString& /* partId */)
          {
          _timeSigDura = Fraction(0, 0);       // invalid
          _octaveShifts.clear();
          _firstInstrSTime = Fraction(0, 1);
          _firstInstrId = "";
          }

    //---------------------------------------------------------
    //   determineMeasureLength
    //---------------------------------------------------------

    /**
     Determine the length in ticks of each measure in all parts.
     Return false on error.
     */

    bool MusicXMLParserPass1::determineMeasureLength(QVector<Fraction>& ml) const
          {
          ml.clear();

          // determine number of measures: max number of measures in any part
          int nMeasures = 0;
          foreach (const MusicXmlPart &part, _parts) {
                if (part.nMeasures() > nMeasures)
                      nMeasures = part.nMeasures();
                }

          // determine max length of a specific measure in all parts
          for (int i = 0; i < nMeasures; ++i) {
                Fraction maxMeasDur;
                foreach (const MusicXmlPart &part, _parts) {
                      if (i < part.nMeasures()) {
                            Fraction measDurPartJ = part.measureDuration(i);
                            if (measDurPartJ > maxMeasDur)
                                  maxMeasDur = measDurPartJ;
                            }
                      }
                //qDebug("determineMeasureLength() measure %d %s (%d)", i, qPrintable(maxMeasDur.print()), maxMeasDur.ticks());
                ml.append(maxMeasDur);
                }
          return true;
          }

    //---------------------------------------------------------
    //   getVoiceList
    //---------------------------------------------------------

    /**
     Get the VoiceList for part \a id.
     Return an empty VoiceList on error.
     */

    VoiceList MusicXMLParserPass1::getVoiceList(const QString id) const
          {
          if (_parts.contains(id))
                return _parts.value(id).voicelist;
          return VoiceList();
          }

    //---------------------------------------------------------
    //   getInstrList
    //---------------------------------------------------------

    /**
     Get the MusicXmlInstrList for part \a id.
     Return an empty MusicXmlInstrList on error.
     */

    MusicXmlInstrList MusicXMLParserPass1::getInstrList(const QString id) const
          {
          if (_parts.contains(id))
                return _parts.value(id)._instrList;
          return MusicXmlInstrList();
          }

    //---------------------------------------------------------
    //   determineMeasureLength
    //---------------------------------------------------------

    /**
     Set default notehead, line and stem direction
     for instrument \a instrId in part \a id.
     */

    void MusicXMLParserPass1::setDrumsetDefault(const QString& id,
                                                const QString& instrId,
                                                const NoteHead::Group hg,
                                                const int line,
                                                const Direction sd)
          {
          if (_drumsets.contains(id)
              && _drumsets[id].contains(instrId)) {
                _drumsets[id][instrId].notehead = hg;
                _drumsets[id][instrId].line = line;
                _drumsets[id][instrId].stemDirection = sd;
                }
          }

    //---------------------------------------------------------
    //   determineStaffMoveVoice
    //---------------------------------------------------------

    /**
     For part \a id, determine MuseScore (ms) staffmove, track and voice from MusicXML (mx) staff and voice
     MusicXML staff is 0 for the first staff, 1 for the second.
     Note: track is the first track of the ms staff in the score, add ms voice for elements in a voice
     Return true if OK, false on error
     TODO: finalize
     */

    bool MusicXMLParserPass1::determineStaffMoveVoice(const QString& id, const int mxStaff, const QString& mxVoice,
                                                      int& msMove, int& msTrack, int& msVoice) const
          {
          VoiceList voicelist = getVoiceList(id);
          msMove = 0; // TODO
          msTrack = 0; // TODO
          msVoice = 0; // TODO

          // Musicxml voices are counted for all staffs of an
          // instrument. They are not limited. In mscore voices are associated
          // with a staff. Every staff can have at most VOICES voices.

          // The following lines map musicXml voices to mscore voices.
          // If a voice crosses two staffs, this is expressed with the
          // "move" parameter in mscore.

          // Musicxml voices are unique within a part, but not across parts.

          //qDebug("voice mapper before: voice='%s' staff=%d", qPrintable(mxVoice), mxStaff);
          int s; // staff mapped by voice mapper
          int v; // voice mapped by voice mapper
          if (voicelist.value(mxVoice).overlaps()) {
                // for overlapping voices, the staff does not change
                // and the voice is mapped and staff-dependent
                s = mxStaff;
                v = voicelist.value(mxVoice).voice(s);
                }
          else {
                // for non-overlapping voices, both staff and voice are
                // set by the voice mapper
                s = voicelist.value(mxVoice).staff();
                v = voicelist.value(mxVoice).voice();
                }

          //qDebug("voice mapper mapped: s=%d v=%d", s, v);
          if (s < 0 || v < 0) {
                qDebug("too many voices (staff=%d voice='%s' -> s=%d v=%d)",
                       mxStaff + 1, qPrintable(mxVoice), s, v);
                return false;
                }

          msMove  = mxStaff - s;
          msVoice = v;

          // make score-relative instead on part-relative
          Part* part = _partMap.value(id);
          Q_ASSERT(part);
          int scoreRelStaff = _score->staffIdx(part); // zero-based number of parts first staff in the score
          msTrack = (scoreRelStaff + s) * VOICES;

          //qDebug("voice mapper after: scoreRelStaff=%d partRelStaff=%d msMove=%d msTrack=%d msVoice=%d",
          //       scoreRelStaff, s, msMove, msTrack, msVoice);
          // note: relStaff is the staff number relative to the parts first staff
          //       voice is the voice number in the staff

          return true;
          }

    //---------------------------------------------------------
    //   hasPart
    //---------------------------------------------------------

    /**
     Check if part \a id is found.
     */

    bool MusicXMLParserPass1::hasPart(const QString& id) const
          {
          return _parts.contains(id);
          }

    //---------------------------------------------------------
    //   trackForPart
    //---------------------------------------------------------

    /**
     Return the (score relative) track number for the first staff of part \a id.
     */

    int MusicXMLParserPass1::trackForPart(const QString& id) const
          {
          Part* part = _partMap.value(id);
          Q_ASSERT(part);
          int scoreRelStaff = _score->staffIdx(part); // zero-based number of parts first staff in the score
          return scoreRelStaff * VOICES;
          }

    //---------------------------------------------------------
    //   getMeasureStart
    //---------------------------------------------------------

    /**
     Return the measure start time for measure \a i.
     */

    Fraction MusicXMLParserPass1::getMeasureStart(const int i) const
          {
          if (0 <= i && i < _measureStart.size())
                return _measureStart.at(i);
          else
                return Fraction(0, 0);       // invalid
          }

    //---------------------------------------------------------
    //   octaveShift
    //---------------------------------------------------------

    /**
     Return the octave shift for part \a id in \a staff at \a f.
     */

    int MusicXMLParserPass1::octaveShift(const QString& id, const int staff, const Fraction f) const
          {
          if (_parts.contains(id))
                return _parts.value(id).octaveShift(staff, f);

          return 0;
          }

    //---------------------------------------------------------
    //   logDebugTrace
    //---------------------------------------------------------

    /**
     Log debug (function) trace.
     */

    void MusicXMLParserPass1::logDebugTrace(const QString& /*info*/)
          {
          //qDebug("Trace %s", qPrintable(info));
          }

    //---------------------------------------------------------
    //   logDebugInfo
    //---------------------------------------------------------

    /**
     Log debug \a info (non-fatal events relevant for debugging).
     */

    void MusicXMLParserPass1::logDebugInfo(const QString& /*info*/)
          {
          //qDebug("Info at line %lld col %lld: %s",
          //       _e.lineNumber(), _e.columnNumber(), qPrintable(info));
          }

    //---------------------------------------------------------
    //   logError
    //---------------------------------------------------------

    /**
     Log \a error (possibly non-fatal but to be reported to the user anyway).
     */

    void MusicXMLParserPass1::logError(const QString& error)
          {
          QString err;
          err = QString("Error at line %1 col %2: %3").arg(_e.lineNumber()).arg(_e.columnNumber()).arg(error);
          qDebug("%s", qPrintable(err));
          _parseStatus += err;
          }

    //---------------------------------------------------------
    //   skipLogCurrElem
    //---------------------------------------------------------

    /**
     Skip the current element, log debug as info.
     */

    void MusicXMLParserPass1::skipLogCurrElem()
          {
          logDebugInfo(QString("skipping '%1'").arg(_e.name().toString()));
          _e.skipCurrentElement();
          }

    //---------------------------------------------------------
    //   createMeasures
    //---------------------------------------------------------

    /**
     Create required measures with correct number, start tick and length for Score \a score.
     */

    static void createMeasures(Score* score, const QVector<Fraction>& ml, const QVector<Fraction>& ms)
          {
          for (int i = 0; i < ml.size(); ++i) {
                Measure* measure  = new Measure(score);
                measure->setTick(ms.at(i).ticks());
                measure->setLen(ml.at(i));
                measure->setNo(i);
                score->measures()->add(measure);
                }
          }

    //---------------------------------------------------------
    //   determineMeasureStart
    //---------------------------------------------------------

    /**
     Determine the start ticks of each measure
     i.e. the sum of all previous measures length
     or start tick measure equals start tick previous measure plus length previous measure
     */

    static void determineMeasureStart(const QVector<Fraction>& ml, QVector<Fraction>& ms)
          {
          ms.resize(ml.size());
          if (!(ms.size() > 0))
                return;  // no parts read

          // first measure starts at t = 0
          ms[0] = Fraction(0, 1);
          // all others start at start time previous measure plus length previous measure
          for (int i = 1; i < ml.size(); i++)
                ms[i] = ms.at(i - 1) + ml.at(i - 1);
          //for (int i = 0; i < ms.size(); i++)
          //      qDebug("measurestart ms[%d] %s", i + 1, qPrintable(ms.at(i).print()));
          }

    //---------------------------------------------------------
    //   addText
    //---------------------------------------------------------

    /**
     Add text \a strTxt to VBox \a vbx using SubStyle \a stl.
     */

    static void addText(VBox* vbx, Score* s, QString strTxt, SubStyle stl)
          {
          if (!strTxt.isEmpty()) {
                Text* text = new Text(stl, s);
                text->setXmlText(strTxt);
                vbx->add(text);
                }
          }

    //---------------------------------------------------------
    //   addText
    //---------------------------------------------------------

    /**
     Add text \a strTxt to VBox \a vbx using SubStyle \a stl.
     Also sets Align and Yoff.
     */

    static void addText2(VBox* vbx, Score* s, QString strTxt, SubStyle stl, Align v, double yoffs)
          {
          if (!strTxt.isEmpty()) {
                Text* text = new Text(stl, s);
                text->setXmlText(strTxt);
                text->setAlign(v);
                text->setOffset(QPointF(0.0, yoffs));
                vbx->add(text);
                }
          }

    //---------------------------------------------------------
    //   doCredits
    //---------------------------------------------------------

    /**
     Create Text elements for the credits read from MusicXML credit-words elements.
     Apply simple heuristics using only default x and y to recognize the meaning of credit words
     If no credits are found, create credits from meta data.
     */

    static void doCredits(Score* score, const CreditWordsList& credits, const int pageWidth, const int pageHeight)
          {
          /*
          qDebug("MusicXml::doCredits()");
          qDebug("page format set (inch) w=%g h=%g tm=%g spatium=%g DPMM=%g DPI=%g",
                 pf->width(), pf->height(), pf->oddTopMargin(), score->spatium(), DPMM, DPI);
          */
          // page width, height and odd top margin in tenths
          const double ph  = score->styleD(StyleIdx::pageHeight) * 10 * DPI / score->spatium();
          const int pw1 = pageWidth / 3;
          const int pw2 = pageWidth * 2 / 3;
          const int ph2 = pageHeight / 2;
          /*
          const double pw  = pf->width() * 10 * DPI / score->spatium();
          const double tm  = pf->oddTopMargin() * 10 * DPI / score->spatium();
          const double tov = ph - tm;
          qDebug("page format set (tenths) w=%g h=%g tm=%g tov=%g", pw, ph, tm, tov);
          qDebug("page format (xml, tenths) w=%d h=%d", pageWidth, pageHeight);
          qDebug("page format pw1=%d pw2=%d ph2=%d", pw1, pw2, ph2);
          */
          // dump the credits
          /*
          for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
                CreditWords* w = *ci;
                qDebug("credit-words defx=%g defy=%g just=%s hal=%s val=%s words='%s'",
                       w->defaultX,
                       w->defaultY,
                       qPrintable(w->justify),
                       qPrintable(w->hAlign),
                       qPrintable(w->vAlign),
                       qPrintable(w->words));
                }
          */

          int nWordsHeader = 0;               // number of credit-words in the header
          int nWordsFooter = 0;               // number of credit-words in the footer
          for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
                CreditWords* w = *ci;
                double defy = w->defaultY;
                // and count #words in header and footer
                if (defy > ph2)
                      nWordsHeader++;
                else
                      nWordsFooter++;
                } // end for (ciCreditWords ...

          // if there are any credit words in the header, use these
          // else use the credit words in the footer (if any)
          bool useHeader = nWordsHeader > 0;
          bool useFooter = nWordsHeader == 0 && nWordsFooter > 0;
          //qDebug("header %d footer %d useHeader %d useFooter %d",
          //       nWordsHeader, nWordsFooter, useHeader, useFooter);

          // determine credits height and create vbox to contain them
          qreal vboxHeight = 10;            // default height in spatium
          double miny = pageHeight;
          double maxy = 0;
          if (pageWidth > 1 && pageHeight > 1) {
                for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
                      CreditWords* w = *ci;
                      double defy = w->defaultY;
                      if ((useHeader && defy > ph2) || (useFooter && defy < ph2)) {
                            if (defy > maxy) maxy = defy;
                            if (defy < miny) miny = defy;
                            }
                      }
                //qDebug("miny=%g maxy=%g", miny, maxy);
                if (miny < (ph - 1) && maxy > 1) {  // if both miny and maxy set
                      double diff = maxy - miny;    // calculate height in tenths
                      if (diff > 1 && diff < ph2) { // and size is reasonable
                            vboxHeight = diff;
                            vboxHeight /= 10;       // height in spatium
                            vboxHeight += 2.5;      // guesstimated correction for last line
                            }
                      }
                }
          //qDebug("vbox height %g sp", vboxHeight);
          VBox* vbox = new VBox(score);
          vbox->setBoxHeight(Spatium(vboxHeight));

          QMap<int, CreditWords*> creditMap;  // store credit-words sorted on y pos
          bool creditWordsUsed = false;

          for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
                CreditWords* w = *ci;
                double defx = w->defaultX;
                double defy = w->defaultY;
                // handle all credit words in the box
                if ((useHeader && defy > ph2) || (useFooter && defy < ph2)) {
                      creditWordsUsed = true;
                      // composer is in the right column
                      if (pw2 < defx) {
                            // found composer
                            addText2(vbox, score, w->words,
                                     SubStyle::COMPOSER, Align::RIGHT | Align::BOTTOM,
                                     (miny - w->defaultY) * score->spatium() / (10 * DPI));
                            }
                      // poet is in the left column
                      else if (defx < pw1) {
                            // found poet/lyricist
                            addText2(vbox, score, w->words,
                                     SubStyle::POET, Align::LEFT | Align::BOTTOM,
                                     (miny - w->defaultY) * score->spatium() / (10 * DPI));
                            }
                      // save others (in the middle column) to be handled later
                      else {
                            creditMap.insert(defy, w);
                            }
                      }
                // keep remaining footer text for possible use as copyright
                else if (useHeader && defy < ph2) {
                      // found credit words in both header and footer
                      // header was used to create a vbox at the top of the first page
                      // footer is ignored as it conflicts with the default MuseScore footer style
                      //qDebug("add to copyright: '%s'", qPrintable(w->words));
                      }
                } // end for (ciCreditWords ...

          /*
           QMap<int, CreditWords*>::const_iterator ci = creditMap.constBegin();
           while (ci != creditMap.constEnd()) {
           CreditWords* w = ci.value();
           qDebug("creditMap %d credit-words defx=%g defy=%g just=%s hal=%s val=%s words=%s",
           ci.key(),
           w->defaultX,
           w->defaultY,
           qPrintable(w->justify),
           qPrintable(w->hAlign),
           qPrintable(w->vAlign),
           qPrintable(w->words));
           ++ci;
           }
           */

          // assign title, subtitle and copyright
          QList<int> keys = creditMap.uniqueKeys(); // note: ignoring credit-words at the same y pos

          // if any credit-words present, the highest is the title
          // note that the keys are sorted in ascending order
          // -> use the last key
          if (keys.size() >= 1) {
                CreditWords* w = creditMap.value(keys.at(keys.size() - 1));
                //qDebug("title='%s'", qPrintable(w->words));
                addText2(vbox, score, w->words,
                         SubStyle::TITLE, Align::HCENTER | Align::TOP,
                         (maxy - w->defaultY) * score->spatium() / (10 * DPI));
                }

          // add remaining credit-words as subtitles
          for (int i = 0; i < (keys.size() - 1); i++) {
                CreditWords* w = creditMap.value(keys.at(i));
                //qDebug("subtitle='%s'", qPrintable(w->words));
                addText2(vbox, score, w->words,
                         SubStyle::SUBTITLE, Align::HCENTER | Align::TOP,
                         (maxy - w->defaultY) * score->spatium() / (10 * DPI));
                }

          // use metadata if no workable credit-words found
          if (!creditWordsUsed) {

                QString strTitle;
                QString strSubTitle;
                QString strComposer;
                QString strPoet;
                QString strTranslator;

                if (!(score->metaTag("movementTitle").isEmpty() && score->metaTag("workTitle").isEmpty())) {
                      strTitle = score->metaTag("movementTitle");
                      if (strTitle.isEmpty())
                            strTitle = score->metaTag("workTitle");
                      }
                if (!(score->metaTag("movementNumber").isEmpty() && score->metaTag("workNumber").isEmpty())) {
                      strSubTitle = score->metaTag("movementNumber");
                      if (strSubTitle.isEmpty())
                            strSubTitle = score->metaTag("workNumber");
                      }
                QString metaComposer = score->metaTag("composer");
                QString metaPoet = score->metaTag("poet");
                QString metaTranslator = score->metaTag("translator");
                if (!metaComposer.isEmpty()) strComposer = metaComposer;
                if (metaPoet.isEmpty()) metaPoet = score->metaTag("lyricist");
                if (!metaPoet.isEmpty()) strPoet = metaPoet;
                if (!metaTranslator.isEmpty()) strTranslator = metaTranslator;

                addText(vbox, score, strTitle.toHtmlEscaped(),      SubStyle::TITLE);
                addText(vbox, score, strSubTitle.toHtmlEscaped(),   SubStyle::SUBTITLE);
                addText(vbox, score, strComposer.toHtmlEscaped(),   SubStyle::COMPOSER);
                addText(vbox, score, strPoet.toHtmlEscaped(),       SubStyle::POET);
                addText(vbox, score, strTranslator.toHtmlEscaped(), SubStyle::TRANSLATOR);
                }

          if (vbox) {
                vbox->setTick(0);
                score->measures()->add(vbox);
                }
          }

    //---------------------------------------------------------
    //   parse
    //---------------------------------------------------------

    /**
     Parse MusicXML in \a device and extract pass 1 data.
     */

    Score::FileError MusicXMLParserPass1::parse(QIODevice* device)
          {
          logDebugTrace("MusicXMLParserPass1::parse device");
          _parts.clear();
          _e.setDevice(device);
          Score::FileError res = parse();
          if (res != Score::FileError::FILE_NO_ERROR)
                return res;

          // Determine the start tick of each measure in the part
          determineMeasureLength(_measureLength);
          determineMeasureStart(_measureLength, _measureStart);
          createMeasures(_score, _measureLength, _measureStart);
          return res;
          }

    //---------------------------------------------------------
    //   parse
    //---------------------------------------------------------

    /**
     Start the parsing process, after verifying the top-level node is score-partwise
     */

    Score::FileError MusicXMLParserPass1::parse()
          {
          logDebugTrace("MusicXMLParserPass1::parse");

          bool found = false;
          while (_e.readNextStartElement()) {
                if (_e.name() == "score-partwise") {
                      found = true;
                      scorePartwise();
                      }
                else {
                      logError(QString("this is not a MusicXML score-partwise file (top-level node '%1')")
                               .arg(_e.name().toString()));
                      _e.skipCurrentElement();
                      return Score::FileError::FILE_BAD_FORMAT;
                      }
                }

          if (!found) {
                logError("this is not a MusicXML score-partwise file, node <score-partwise> not found");
                return Score::FileError::FILE_BAD_FORMAT;
                }

          return Score::FileError::FILE_NO_ERROR;
          }

    //---------------------------------------------------------
    //   allStaffGroupsIdentical
    //---------------------------------------------------------

    /**
     Return true if all staves in Part \a p have the same staff group
     */

    static bool allStaffGroupsIdentical(Part const* const p)
          {
          for (int i = 1; i < p->nstaves(); ++i) {
                if (p->staff(0)->staffType(0)->group() != p->staff(i)->staffType(0)->group())
                      return false;
                }
          return true;
          }

    //---------------------------------------------------------
    //   scorePartwise
    //---------------------------------------------------------

    /**
     Parse the MusicXML top-level (XPath /score-partwise) node.
     */

    void MusicXMLParserPass1::scorePartwise()
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "score-partwise");
          logDebugTrace("MusicXMLParserPass1::scorePartwise");

          MusicXmlPartGroupList partGroupList;
          CreditWordsList credits;
          int pageWidth;                             ///< Page width read from defaults
          int pageHeight;                            ///< Page height read from defaults

          while (_e.readNextStartElement()) {
                if (_e.name() == "part")
                      part();
                else if (_e.name() == "part-list") {
                      // if any credits are present, they have been read now
                      // add the credits to the score before adding any measure
                      // note that a part-list element must always be present
                      doCredits(_score, credits, pageWidth, pageHeight);
                      // and read the part list
                      partList(partGroupList);
                      }
                else if (_e.name() == "work") {
                      while (_e.readNextStartElement()) {
                            if (_e.name() == "work-number")
                                  _score->setMetaTag("workNumber", _e.readElementText());
                            else if (_e.name() == "work-title")
                                  _score->setMetaTag("workTitle", _e.readElementText());
                            else
                                  skipLogCurrElem();
                            }
                      }
                else if (_e.name() == "identification")
                      identification();
                else if (_e.name() == "defaults")
                      defaults(pageWidth, pageHeight);
                else if (_e.name() == "movement-number")
                      _score->setMetaTag("movementNumber", _e.readElementText());
                else if (_e.name() == "movement-title")
                      _score->setMetaTag("movementTitle", _e.readElementText());
                else if (_e.name() == "credit")
                      credit(credits);
                else
                      skipLogCurrElem();
                }

          // add brackets where required

          /*
           qDebug("partGroupList");
           for (int i = 0; i < (int) partGroupList.size(); i++) {
           MusicXmlPartGroup* pg = partGroupList[i];
           qDebug("part-group span %d start %d type %hhd barlinespan %d",
           pg->span, pg->start, pg->type, pg->barlineSpan);
           }
           */

          // set of (typically multi-staff) parts containing one or more explicit brackets
          // spanning only that part: these won't get an implicit brace later
          // e.g. a two-staff piano part with an explicit brace
          QSet<Part const* const> partSet;

          // handle the explicit brackets
          const QList<Part*>& il = _score->parts();
          for (int i = 0; i < (int) partGroupList.size(); i++) {
                MusicXmlPartGroup* pg = partGroupList[i];
                // add part to set
                if (pg->span == 1)
                      partSet << il.at(pg->start);
                // determine span in staves
                int stavesSpan = 0;
                for (int j = 0; j < pg->span; j++)
                      stavesSpan += il.at(pg->start + j)->nstaves();
                // add bracket and set the span
                // TODO: use group-symbol default-x to determine horizontal order of brackets
                Staff* staff = il.at(pg->start)->staff(0);
                if (pg->type == BracketType::NO_BRACKET)
                      staff->setBracketType(0, BracketType::NO_BRACKET);
                else {
                      staff->addBracket(new BracketItem(staff->score(), pg->type, stavesSpan));
                      }
                if (pg->barlineSpan)
                      staff->setBarLineSpan(pg->span);
                }

          // handle the implicit brackets:
          // multi-staff parts w/o explicit brackets get a brace
          foreach(Part const* const p, il) {
                if (p->nstaves() > 1 && !partSet.contains(p)) {
                      p->staff(0)->addBracket(new BracketItem(p->score(), BracketType::BRACE, p->nstaves()));
                      if (allStaffGroupsIdentical(p)) {
                            // span only if the same types
                            p->staff(0)->setBarLineSpan(p->nstaves());
                            }
                      }
                }
          }

    //---------------------------------------------------------
    //   identification
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/identification node:
     read the metadata.
     */

    void MusicXMLParserPass1::identification()
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "identification");
          logDebugTrace("MusicXMLParserPass1::identification");

          while (_e.readNextStartElement()) {
                if (_e.name() == "creator") {
                      // type is an arbitrary label
                      QString strType = _e.attributes().value("type").toString();
                      _score->setMetaTag(strType, _e.readElementText());
                      }
                else if (_e.name() == "rights")
                      _score->setMetaTag("copyright", _e.readElementText());
                else if (_e.name() == "encoding") {
                      // TODO
                      _e.skipCurrentElement(); // skip but don't log
                      // _score->setMetaTag("encoding", _e.readElementText()); works with DOM but not with pull parser
                      // temporarily fake the encoding tag (compliant with DOM parser) to help the autotester
                      if (MScore::debugMode)
                            _score->setMetaTag("encoding", "MuseScore 0.7.02007-09-10");
                      }
                else if (_e.name() == "source")
                      _score->setMetaTag("source", _e.readElementText());
                else if (_e.name() == "miscellaneous")
                      // TODO
                      _e.skipCurrentElement();  // skip but don't log
                else
                      skipLogCurrElem();
                }
          }

    //---------------------------------------------------------
    //   text2syms
    //---------------------------------------------------------

    /**
     Convert SMuFL code points to MuseScore <sym>...</sym>
     */

    static QString text2syms(const QString& t)
          {
          //QTime time;
          //time.start();

          // first create a map from symbol (Unicode) text to symId
          // note that this takes about 1 msec on a Core i5,
          // caching does not gain much

          ScoreFont* sf = ScoreFont::fallbackFont();
          QMap<QString, SymId> map;
          int maxStringSize = 0;        // maximum string size found

          for (int i = int(SymId::noSym); i < int(SymId::lastSym); ++i) {
                SymId id((SymId(i)));
                QString string(sf->toString(id));
                // insert all syms except space to prevent matching all regular spaces
                if (id != SymId::space)
                      map.insert(string, id);
                if (string.size() > maxStringSize)
                      maxStringSize = string.size();
                }
          //qDebug("text2syms map count %d maxsz %d filling time elapsed: %d ms",
          //       map.size(), maxStringSize, time.elapsed());

          // then look for matches
          QString in = t;
          QString res;

          while (in != "") {
                // try to find the largest match possible
                int maxMatch = qMin(in.size(), maxStringSize);
                QString sym;
                while (maxMatch > 0) {
                      QString toBeMatched = in.left(maxMatch);
                      if (map.contains(toBeMatched)) {
                            sym = Sym::id2name(map.value(toBeMatched));
                            break;
                            }
                      maxMatch--;
                      }
                if (maxMatch > 0) {
                      // found a match, add sym to res and remove match from string in
                      res += "<sym>";
                      res += sym;
                      res += "</sym>";
                      in.remove(0, maxMatch);
                      }
                else {
                      // not found, move one char from res to in
                      res += in.left(1);
                      in.remove(0, 1);
                      }
                }

          //qDebug("text2syms total time elapsed: %d ms, res '%s'", time.elapsed(), qPrintable(res));
          return res;
          }

    //---------------------------------------------------------
    //   decodeEntities
    //---------------------------------------------------------

    /**
     Decode &#...; in string \a src into UNICODE (utf8) character.
     */

    static QString decodeEntities( const QString& src )
          {
          QString ret(src);
          QRegExp re("&#([0-9]+);");
          re.setMinimal(true);

          int pos = 0;
          while ( (pos = re.indexIn(src, pos)) != -1 ) {
                ret = ret.replace(re.cap(0), QChar(re.cap(1).toInt(0,10)));
                pos += re.matchedLength();
                }
          return ret;
          }

    //---------------------------------------------------------
    //   nextPartOfFormattedString
    //---------------------------------------------------------

    // TODO: probably should be shared between pass 1 and 2

    /**
     Read the next part of a MusicXML formatted string and convert to MuseScore internal encoding.
     */

    static QString nextPartOfFormattedString(QXmlStreamReader& e)
          {
          //QString lang       = e.attribute(QString("xml:lang"), "it");
          QString fontWeight = e.attributes().value("font-weight").toString();
          QString fontSize   = e.attributes().value("font-size").toString();
          QString fontStyle  = e.attributes().value("font-style").toString();
          QString underline  = e.attributes().value("underline").toString();
          QString fontFamily = e.attributes().value("font-family").toString();
          // TODO: color, enclosure, yoffset in only part of the text, ...

          QString txt        = e.readElementText();
          // replace HTML entities
          txt = decodeEntities(txt);
          QString syms       = text2syms(txt);

          QString importedtext;

          if (!fontSize.isEmpty()) {
                bool ok = true;
                float size = fontSize.toFloat(&ok);
                if (ok)
                      importedtext += QString("<font size=\"%1\"/>").arg(size);
                }
          if (!fontFamily.isEmpty() && txt == syms) {
                // add font family only if no <sym> replacement made
                importedtext += QString("<font face=\"%1\"/>").arg(fontFamily);
                }
          if (fontWeight == "bold")
                importedtext += "<b>";
          if (fontStyle == "italic")
                importedtext += "<i>";
          if (!underline.isEmpty()) {
                bool ok = true;
                int lines = underline.toInt(&ok);
                if (ok && (lines > 0))  // 1,2, or 3 underlines are imported as single underline
                      importedtext += "<u>";
                else
                      underline = "";
                }
          if (txt == syms) {
                txt.replace(QString("\r"), QString("")); // convert Windows line break \r\n -> \n
                importedtext += txt.toHtmlEscaped();
                }
          else {
                // <sym> replacement made, should be no need for line break or other conversions
                importedtext += syms;
                }
          if (underline != "")
                importedtext += "</u>";
          if (fontStyle == "italic")
                importedtext += "</i>";
          if (fontWeight == "bold")
                importedtext += "</b>";
          //qDebug("importedtext '%s'", qPrintable(importedtext));
          return importedtext;
          }

    //---------------------------------------------------------
    //   credit
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/credit node:
     read the credits for later handling by doCredits().
     */

    void MusicXMLParserPass1::credit(CreditWordsList& credits)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "credit");
          logDebugTrace("MusicXMLParserPass1::credit");

          QString page = _e.attributes().value("page").toString();
          // handle only page 1 credits (to extract title etc.)
          // assume no page attribute means page 1
          if (page == "" || page == "1") {
                // multiple credit-words elements may be present,
                // which are appended
                // use the position info from the first one
                // font information is ignored, credits will be styled
                bool creditWordsRead = false;
                double defaultx = 0;
                double defaulty = 0;
                QString justify;
                QString halign;
                QString valign;
                QString crwords;
                while (_e.readNextStartElement()) {
                      if (_e.name() == "credit-words") {
                            // IMPORT_LAYOUT
                            if (!creditWordsRead) {
                                  defaultx = _e.attributes().value("default-x").toString().toDouble();
                                  defaulty = _e.attributes().value("default-y").toString().toDouble();
                                  justify  = _e.attributes().value("justify").toString();
                                  halign   = _e.attributes().value("halign").toString();
                                  valign   = _e.attributes().value("valign").toString();
                                  creditWordsRead = true;
                                  }
                            crwords += nextPartOfFormattedString(_e);
                            }
                      else if (_e.name() == "credit-type")
                            skipLogCurrElem();
                      else
                            skipLogCurrElem();
                      }
                if (crwords != "") {
                      CreditWords* cw = new CreditWords(defaultx, defaulty, justify, halign, valign, crwords);
                      credits.append(cw);
                      }
                }
          else
                skipLogCurrElem();

          Q_ASSERT(_e.isEndElement() && _e.name() == "credit");
          }

    //---------------------------------------------------------
    //   mustSetSize
    //---------------------------------------------------------

    /**
     Determine if i is a style type for which the default size must be set
     */

    // The MusicXML specification does not specify to which kinds of text
    // the word-font setting applies. Setting all sizes to the size specified
    // gives bad results, e.g. for measure numbers, so a selection is made.
    // Some tweaking may still be required.
    #if 0
    static bool mustSetSize(const int i)
          {
          return
                i == int(SubStyle::TITLE)
                || i == int(SubStyle::SUBTITLE)
                || i == int(SubStyle::COMPOSER)
                || i == int(SubStyle::POET)
                || i == int(SubStyle::INSTRUMENT_LONG)
                || i == int(SubStyle::INSTRUMENT_SHORT)
                || i == int(SubStyle::INSTRUMENT_EXCERPT)
                || i == int(SubStyle::TEMPO)
                || i == int(SubStyle::METRONOME)
                || i == int(SubStyle::TRANSLATOR)
                || i == int(SubStyle::SYSTEM)
                || i == int(SubStyle::STAFF)
                || i == int(SubStyle::REPEAT_LEFT)
                || i == int(SubStyle::REPEAT_RIGHT)
                || i == int(SubStyle::TEXTLINE)
                || i == int(SubStyle::GLISSANDO)
                || i == int(SubStyle::INSTRUMENT_CHANGE);
          }
    #endif

    //---------------------------------------------------------
    //   updateStyles
    //---------------------------------------------------------

    /**
     Update the style definitions to match the MusicXML word-font and lyric-font.
     */

    static void updateStyles(Score* score,
                             const QString& /*wordFamily*/, const QString& /*wordSize*/,
                             const QString& lyricFamily, const QString& lyricSize)
          {
    //TODO:ws       const float fWordSize = wordSize.toFloat();   // note conversion error results in value 0.0
          const float fLyricSize = lyricSize.toFloat(); // but avoid comparing float with exact value later

          // loop over all text styles (except the empty, always hidden, first one)
          // set all text styles to the MusicXML defaults
    #if 0 // TODO:ws
          for (int i = int(SubStyle::DEFAULT) + 1; i < int(SubStyle::TEXT_STYLES); ++i) {
                TextStyle ts = score->style().textStyle(TextStyleType(i));
                if (i == int(SubStyle::LYRIC1) || i == int(SubStyle::LYRIC2)) {
                      if (lyricFamily != "")
                            ts.setFamily(lyricFamily);
                      if (fLyricSize > 0.001)
                            ts.setSize(fLyricSize);
                      }
                else {
                      if (wordFamily != "")
                            ts.setFamily(wordFamily);
                      if (fWordSize > 0.001 && mustSetSize(i))
                            ts.setSize(fWordSize);
                      }
                score->style().setTextStyle(ts);
                }
    #endif
          if (lyricFamily != "") {
                score->style().set(StyleIdx::lyricsOddFontFace, lyricFamily);
                score->style().set(StyleIdx::lyricsEvenFontFace, lyricFamily);
                }
          if (fLyricSize > 0.001) {
                score->style().set(StyleIdx::lyricsOddFontSize, fLyricSize);
                score->style().set(StyleIdx::lyricsEvenFontSize, fLyricSize);
                }
          }

    //---------------------------------------------------------
    //   defaults
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/defaults node:
     read the general score layout settings.
     */

    void MusicXMLParserPass1::defaults(int& pageWidth, int& pageHeight)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "defaults");
          logDebugTrace("MusicXMLParserPass1::defaults");

          double millimeter = _score->spatium()/10.0;
          double tenths = 1.0;
          QString lyricFontFamily;
          QString lyricFontSize;
          QString wordFontFamily;
          QString wordFontSize;

          while (_e.readNextStartElement()) {
                if (_e.name() == "scaling") {
                      while (_e.readNextStartElement()) {
                            if (_e.name() == "millimeters")
                                  millimeter = _e.readElementText().toDouble();
                            else if (_e.name() == "tenths")
                                  tenths = _e.readElementText().toDouble();
                            else
                                  skipLogCurrElem();
                            }
                      double _spatium = DPMM * (millimeter * 10.0 / tenths);
                      if (preferences.musicxmlImportLayout)
                            _score->setSpatium(_spatium);
                      }
                else if (_e.name() == "page-layout") {
                      PageFormat pf;
                      pageLayout(pf, millimeter / (tenths * INCH), pageWidth, pageHeight);
    //TODO:ws                  if (preferences.musicxmlImportLayout)
    //                        _score->setPageFormat(pf);
                      }
                else if (_e.name() == "system-layout") {
                      while (_e.readNextStartElement()) {
                            if (_e.name() == "system-margins")
                                  skipLogCurrElem();
                            else if (_e.name() == "system-distance") {
                                  Spatium val(_e.readElementText().toDouble() / 10.0);
                                  if (preferences.musicxmlImportLayout) {
                                        _score->style().set(StyleIdx::minSystemDistance, val);
                                        qDebug("system distance %f", val.val());
                                        }
                                  }
                            else if (_e.name() == "top-system-distance")
                                  skipLogCurrElem();
                            else
                                  skipLogCurrElem();
                            }
                      }
                else if (_e.name() == "staff-layout") {
                      while (_e.readNextStartElement()) {
                            if (_e.name() == "staff-distance") {
                                  Spatium val(_e.readElementText().toDouble() / 10.0);
                                  if (preferences.musicxmlImportLayout)
                                        _score->style().set(StyleIdx::staffDistance, val);
                                  }
                            else
                                  skipLogCurrElem();
                            }
                      }
                else if (_e.name() == "music-font")
                      skipLogCurrElem();
                else if (_e.name() == "word-font") {
                      wordFontFamily = _e.attributes().value("font-family").toString();
                      wordFontSize = _e.attributes().value("font-size").toString();
                      _e.skipCurrentElement();
                      }
                else if (_e.name() == "lyric-font") {
                      lyricFontFamily = _e.attributes().value("font-family").toString();
                      lyricFontSize = _e.attributes().value("font-size").toString();
                      _e.skipCurrentElement();
                      }
                else if (_e.name() == "appearance")
                      skipLogCurrElem();
                else if (_e.name() == "lyric-language")
                      skipLogCurrElem();
                else
                      skipLogCurrElem();
                }

          /*
           qDebug("word font family '%s' size '%s' lyric font family '%s' size '%s'",
           qPrintable(wordFontFamily), qPrintable(wordFontSize),
           qPrintable(lyricFontFamily), qPrintable(lyricFontSize));
           */
          updateStyles(_score, wordFontFamily, wordFontSize, lyricFontFamily, lyricFontSize);

          _score->setDefaultsRead(true); // TODO only if actually succeeded ?
          }

    //---------------------------------------------------------
    //   pageLayout
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/defaults/page-layout node:
     read the page layout.
     */

    void MusicXMLParserPass1::pageLayout(PageFormat& pf, const qreal conversion,
                                         int& pageWidth, int& pageHeight)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "page-layout");
          logDebugTrace("MusicXMLParserPass1::pageLayout");

          qreal _oddRightMargin  = 0.0;
          qreal _evenRightMargin = 0.0;
          QSizeF size;

          while (_e.readNextStartElement()) {
                if (_e.name() == "page-margins") {
                      QString type = _e.attributes().value("type").toString();
                      if (type == "")
                            type = "both";
                      qreal lm = 0.0, rm = 0.0, tm = 0.0, bm = 0.0;
                      while (_e.readNextStartElement()) {
                            if (_e.name() == "left-margin")
                                  lm = _e.readElementText().toDouble() * conversion;
                            else if (_e.name() == "right-margin")
                                  rm = _e.readElementText().toDouble() * conversion;
                            else if (_e.name() == "top-margin")
                                  tm = _e.readElementText().toDouble() * conversion;
                            else if (_e.name() == "bottom-margin")
                                  bm = _e.readElementText().toDouble() * conversion;
                            else
                                  skipLogCurrElem();
                            }
                      pf.twosided = type == "odd" || type == "even";
                      if (type == "odd" || type == "both") {
                            pf.oddLeftMargin = lm;
                            _oddRightMargin = rm;
                            pf.oddTopMargin = tm;
                            pf.oddBottomMargin = bm;
                            }
                      if (type == "even" || type == "both") {
                            pf.evenLeftMargin = lm;
                            _evenRightMargin = rm;
                            pf.evenTopMargin = tm;
                            pf.evenBottomMargin = bm;
                            }
                      }
                else if (_e.name() == "page-height") {
                      double val = _e.readElementText().toDouble();
                      size.rheight() = val * conversion;
                      // set pageHeight and pageWidth for use by doCredits()
                      pageHeight = static_cast<int>(val + 0.5);
                      }
                else if (_e.name() == "page-width") {
                      double val = _e.readElementText().toDouble();
                      size.rwidth() = val * conversion;
                      // set pageHeight and pageWidth for use by doCredits()
                      pageWidth = static_cast<int>(val + 0.5);
                      }
                else
                      skipLogCurrElem();
                }
          pf.size = size;
          qreal w1 = size.width() - pf.oddLeftMargin - _oddRightMargin;
          qreal w2 = size.width() - pf.evenLeftMargin - _evenRightMargin;
          pf.printableWidth = qMax(w1, w2);   // silently adjust right margins
          }

    //---------------------------------------------------------
    //   partList
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part-list:
     create the parts and for each part set id and name.
     Also handle the part-groups.
     */

    void MusicXMLParserPass1::partList(MusicXmlPartGroupList& partGroupList)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "part-list");
          logDebugTrace("MusicXMLParserPass1::partList");

          int scoreParts = 0; // number of score-parts read sofar
          MusicXmlPartGroupMap partGroups;

          while (_e.readNextStartElement()) {
                if (_e.name() == "part-group")
                      partGroup(scoreParts, partGroupList, partGroups);
                else if (_e.name() == "score-part") {
                      scorePart();
                      scoreParts++;
                      }
                else
                      skipLogCurrElem();
                }
          }

    //---------------------------------------------------------
    //   createPart
    //---------------------------------------------------------

    /**
     Create the part, set its \a id and insert it in PartMap \a pm.
     Part name (if any) will be set later.
     */

    static void createPart(Score* score, const QString& id, PartMap& pm)
          {
          Part* part = new Part(score);
          pm.insert(id, part);
          part->setId(id);
          score->appendPart(part);
          Staff* staff = new Staff(score);
          staff->setPart(part);
          part->staves()->push_back(staff);
          score->staves().push_back(staff);
          // TODO TBD tuplets.resize(VOICES); // part now contains one staff, thus VOICES voices
          }

    //---------------------------------------------------------
    //   partGroupStart
    //---------------------------------------------------------

    typedef std::map<int,MusicXmlPartGroup*> MusicXmlPartGroupMap;

    /**
     Store part-group start with number \a n, first part \a p and symbol / \a s in the partGroups
     map \a pgs for later reference, as at this time insufficient information is available to be able
     to generate the brackets.
     */

    static void partGroupStart(MusicXmlPartGroupMap& pgs, int n, int p, QString s, bool barlineSpan)
          {
          //qDebug("partGroupStart number=%d part=%d symbol=%s", n, p, qPrintable(s));

          if (pgs.count(n) > 0) {
                qDebug("part-group number=%d already active", n);
                return;
                }

          BracketType bracketType = BracketType::NO_BRACKET;
          if (s == "")
                ;        // ignore (handle as NO_BRACKET)
          else if (s == "none")
                ;        // already set to NO_BRACKET
          else if (s == "brace")
                bracketType = BracketType::BRACE;
          else if (s == "bracket")
                bracketType = BracketType::NORMAL;
          else if (s == "line")
                bracketType = BracketType::LINE;
          else if (s == "square")
                bracketType = BracketType::SQUARE;
          else {
                qDebug("part-group symbol=%s not supported", qPrintable(s));
                return;
                }

          MusicXmlPartGroup* pg = new MusicXmlPartGroup;
          pg->span = 0;
          pg->start = p;
          pg->barlineSpan = barlineSpan,
          pg->type = bracketType;
          pgs[n] = pg;
          }

    //---------------------------------------------------------
    //   partGroupStop
    //---------------------------------------------------------

    /**
     Handle part-group stop with number \a n and part \a p.

     For part group n, the start part, span (in parts) and type are now known.
     To generate brackets, the span in staves must also be known.
     */

    static void partGroupStop(MusicXmlPartGroupMap& pgs, int n, int p,
                              MusicXmlPartGroupList& pgl)
          {
          if (pgs.count(n) == 0) {
                qDebug("part-group number=%d not active", n);
                return;
                }

          pgs[n]->span = p - pgs[n]->start;
          //qDebug("partgroupstop number=%d start=%d span=%d type=%hhd",
          //       n, pgs[n]->start, pgs[n]->span, pgs[n]->type);
          pgl.push_back(pgs[n]);
          pgs.erase(n);
          }

    //---------------------------------------------------------
    //   partGroup
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part-list/part-group node.
     */

    void MusicXMLParserPass1::partGroup(const int scoreParts,
                                        MusicXmlPartGroupList& partGroupList,
                                        MusicXmlPartGroupMap& partGroups)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "part-group");
          logDebugTrace("MusicXMLParserPass1::partGroup");
          bool barlineSpan = true;
          int number = _e.attributes().value("number").toInt();
          if (number > 0) number--;
          QString symbol = "";
          QString type = _e.attributes().value("type").toString();

          while (_e.readNextStartElement()) {
                if (_e.name() == "group-symbol")
                      symbol = _e.readElementText();
                else if (_e.name() == "group-barline") {
                      if (_e.readElementText() == "no")
                            barlineSpan = false;
                      }
                else
                      skipLogCurrElem();
                }

          if (type == "start")
                partGroupStart(partGroups, number, scoreParts, symbol, barlineSpan);
          else if (type == "stop")
                partGroupStop(partGroups, number, scoreParts, partGroupList);
          else
                qDebug("MusicXMLParserPass1::partGroup: part-group type '%s' not supported",
                       qPrintable(type));  // TODO
          }

    //---------------------------------------------------------
    //   findInstrument
    //---------------------------------------------------------

    /**
     Find the first InstrumentTemplate with musicXMLid instrSound
     and a non-empty set of channels.
     */

    static const InstrumentTemplate* findInstrument(const QString& instrSound)
          {
          const InstrumentTemplate* instr = nullptr;

          for (const InstrumentGroup* group : instrumentGroups) {
                for (const InstrumentTemplate* templ : group->instrumentTemplates) {
                      if (templ->musicXMLid == instrSound && !templ->channel.isEmpty()) {
                            return templ;
                            }
                      }
                }
          return instr;
          }

    //---------------------------------------------------------
    //   fixupMidiProgram
    //---------------------------------------------------------

    static void fixupMidiProgram(MusicXMLDrumset& drumset)
          {
          for (auto& instr : drumset) {
                if (instr.midiProgram < 0 && instr.sound != "") {
                      const InstrumentTemplate* templ = findInstrument(instr.sound);
                      if (templ) {
                            const int prog = templ->channel.at(0).program;
                            instr.midiProgram = prog;
                            }
                      }
                }
          }

    //---------------------------------------------------------
    //   scorePart
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part-list/score-part node:
     create the part and sets id and name.
     Note that a part is created even if no part-name is present
     which is invalid MusicXML but is (sometimes ?) generated by NWC2MusicXML.
     */

    void MusicXMLParserPass1::scorePart()
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "score-part");
          logDebugTrace("MusicXMLParserPass1::scorePart");
          QString id = _e.attributes().value("id").toString();

          if (_parts.contains(id)) {
                logError(QString("duplicate part id '%1'").arg(id));
                skipLogCurrElem();
                return;
                }
          else {
                _parts.insert(id, MusicXmlPart(id));
                _drumsets.insert(id, MusicXMLDrumset());
                createPart(_score, id, _partMap);
                }

          while (_e.readNextStartElement()) {
                if (_e.name() == "part-name") {
                      // Element part-name contains the displayed (full) part name
                      // It is displayed by default, but can be suppressed (print-object=”no”)
                      // As of MusicXML 3.0, formatting is deprecated, with part-name in plain text
                      // and the formatted version in the part-name-display element
                      bool doLong = !(_e.attributes().value("print-object") == "no");
                      QString name = _e.readElementText();
                      _partMap[id]->setPartName(name);
                      if (doLong)
                            _partMap[id]->setLongName(name);
                      }
                else if (_e.name() == "part-name-display") {
                      // TODO
                      _e.skipCurrentElement(); // skip but don't log
                      }
                else if (_e.name() == "part-abbreviation") {
                      // Element part-name contains the displayed (abbreviated) part name
                      // It is displayed by default, but can be suppressed (print-object=”no”)
                      // As of MusicXML 3.0, formatting is deprecated, with part-name in plain text
                      // and the formatted version in the part-abbreviation-display element
                      QString name = _e.readElementText();
                      if (!(_e.attributes().value("print-object") == "no"))
                            _partMap[id]->setPlainShortName(name);
                      }
                else if (_e.name() == "part-abbreviation-display")
                      _e.skipCurrentElement();  // skip but don't log
                else if (_e.name() == "score-instrument")
                      scoreInstrument(id);
                else if (_e.name() == "midi-device") {
                      if (!_e.attributes().hasAttribute("port")) {
                            _e.readElementText(); // empty string
                            continue;
                            }
                      QString instrId = _e.attributes().value("id").toString();
                      QString port = _e.attributes().value("port").toString();
                      // If instrId is missing, the device assignment affects all
                      // score-instrument elements in the score-part
                      if (instrId.isEmpty()) {
                            for (auto it = _drumsets[id].cbegin(); it != _drumsets[id].cend(); ++it)
                                  _drumsets[id][it.key()].midiPort = port.toInt() - 1;
                            }
                      else if (_drumsets[id].contains(instrId))
                            _drumsets[id][instrId].midiPort = port.toInt() - 1;

                      _e.readElementText(); // empty string
                      }
                else if (_e.name() == "midi-instrument")
                      midiInstrument(id);
                else
                      skipLogCurrElem();
                }

          fixupMidiProgram(_drumsets[id]);

          Q_ASSERT(_e.isEndElement() && _e.name() == "score-part");
          }

    //---------------------------------------------------------
    //   scoreInstrument
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part-list/score-part/score-instrument node.
     */

    void MusicXMLParserPass1::scoreInstrument(const QString& partId)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "score-instrument");
          logDebugTrace("MusicXMLParserPass1::scoreInstrument");
          QString instrId = _e.attributes().value("id").toString();

          while (_e.readNextStartElement()) {
                if (_e.name() == "ensemble")
                      skipLogCurrElem();
                else if (_e.name() == "instrument-name") {
                      QString instrName = _e.readElementText();
                      /*
                      qDebug("partId '%s' instrId '%s' instrName '%s'",
                             qPrintable(partId),
                             qPrintable(instrId),
                             qPrintable(instrName)
                             );
                       */
                      _drumsets[partId].insert(instrId, MusicXMLDrumInstrument(instrName));
                      // Element instrument-name is typically not displayed in the score,
                      // but used only internally
                      if (_drumsets[partId].contains(instrId))
                            _drumsets[partId][instrId].name = instrName;
                      // try to prevent an empty track name
                      if (_partMap[partId]->partName() == "")
                            _partMap[partId]->setPartName(instrName);
                      }
                else if (_e.name() == "instrument-sound") {
                      QString instrSound = _e.readElementText();
                      if (_drumsets[partId].contains(instrId))
                            _drumsets[partId][instrId].sound = instrSound;
                      }
                else if (_e.name() == "virtual-instrument") {
                      while (_e.readNextStartElement()) {
                            if (_e.name() == "virtual-library") {
                                  QString virtualLibrary = _e.readElementText();
                                  if (_drumsets[partId].contains(instrId))
                                        _drumsets[partId][instrId].virtLib = virtualLibrary;
                                  }
                            else if (_e.name() == "virtual-name") {
                                  QString virtualName = _e.readElementText();
                                  if (_drumsets[partId].contains(instrId))
                                        _drumsets[partId][instrId].virtName = virtualName;
                                  }
                            else
                                  skipLogCurrElem();
                            }
                      }
                else
                      skipLogCurrElem();
                }
          Q_ASSERT(_e.isEndElement() && _e.name() == "score-instrument");
          }

    //---------------------------------------------------------
    //   midiInstrument
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part-list/score-part/midi-instrument node.
     */

    void MusicXMLParserPass1::midiInstrument(const QString& partId)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "midi-instrument");
          logDebugTrace("MusicXMLParserPass1::midiInstrument");
          QString instrId = _e.attributes().value("id").toString();

          while (_e.readNextStartElement()) {
                if (_e.name() == "midi-bank")
                      skipLogCurrElem();
                else if (_e.name() == "midi-channel") {
                      int channel = _e.readElementText().toInt();
                      if (channel < 1) {
                            logError(QString("MusicXml::xmlScorePart: incorrect midi-channel: %1").arg(channel));
                            channel = 1;
                            }
                      else if (channel > 16) {
                            logError(QString("MusicXml::xmlScorePart: incorrect midi-channel: %1").arg(channel));
                            channel = 16;
                            }
                      if (_drumsets[partId].contains(instrId))
                            _drumsets[partId][instrId].midiChannel = channel - 1;
                      }
                else if (_e.name() == "midi-program") {
                      int program = _e.readElementText().toInt();
                      // Bug fix for Cubase 6.5.5 which generates <midi-program>0</midi-program>
                      // Check program number range
                      if (program < 1) {
                            logError(QString("MusicXml::xmlScorePart: incorrect midi-program: %1").arg(program));
                            program = 1;
                            }
                      else if (program > 128) {
                            logError(QString("MusicXml::xmlScorePart: incorrect midi-program: %1").arg(program));
                            program = 128;
                            }
                      if (_drumsets[partId].contains(instrId))
                            _drumsets[partId][instrId].midiProgram = program - 1;
                      }
                else if (_e.name() == "midi-unpitched") {
                      if (_drumsets[partId].contains(instrId))
                            _drumsets[partId][instrId].pitch = _e.readElementText().toInt() - 1;
                      }
                else if (_e.name() == "volume") {
                      double vol = _e.readElementText().toDouble();
                      if (vol >= 0 && vol <= 100) {
                            if (_drumsets[partId].contains(instrId))
                                  _drumsets[partId][instrId].midiVolume = static_cast<int>((vol / 100) * 127);
                            }
                      else
                            logError(QString("MusicXml::xmlScorePart: incorrect midi-volume: %1").arg(vol));
                      }
                else if (_e.name() == "pan") {
                      double pan = _e.readElementText().toDouble();
                      if (pan >= -90 && pan <= 90) {
                            if (_drumsets[partId].contains(instrId))
                                  _drumsets[partId][instrId].midiPan = static_cast<int>(((pan + 90) / 180) * 127);
                            }
                      else
                            logError(QString("MusicXml::xmlScorePart: incorrect midi-volume: %g1").arg(pan));
                      }
                else
                      skipLogCurrElem();
                }
          Q_ASSERT(_e.isEndElement() && _e.name() == "midi-instrument");
          }

    //---------------------------------------------------------
    //   part
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part node:
     read the parts data to determine measure timing and octave shifts.
     Assign voices and staves.
     */

    void MusicXMLParserPass1::part()
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "part");
          logDebugTrace("MusicXMLParserPass1::part");
          const QString id = _e.attributes().value("id").toString();

          if (!_parts.contains(id)) {
                logError(QString("MusicXMLParserPass1::part cannot find part '%1'").arg(id));
                skipLogCurrElem();
                }

          initPartState(id);

          VoiceOverlapDetector vod;
          Fraction time;  // current time within part
          Fraction mdur;  // measure duration

          while (_e.readNextStartElement()) {
                if (_e.name() == "measure") {
                      measure(id, time, mdur, vod);
                      time += mdur;
                      }
                else
                      skipLogCurrElem();
                }

          // allocate MuseScore staff to MusicXML voices
          allocateStaves(_parts[id].voicelist);
          // allocate MuseScore voice to MusicXML voices
          allocateVoices(_parts[id].voicelist);
          // calculate the octave shifts
          _parts[id].calcOctaveShifts();
          // set first instrument for multi-instrument part starting with rest
          if (_firstInstrId != "" && _firstInstrSTime > Fraction(0, 1))
                _parts[id]._instrList.setInstrument(_firstInstrId, Fraction(0, 1));

          // debug: print results
          /*
          qDebug("voiceMapperStats: new staff");
          VoiceList& vl = _parts[id].voicelist;
          for (auto i = vl.constBegin(); i != vl.constEnd(); ++i) {
                qDebug("voiceMapperStats: voice %s staff data %s",
                       qPrintable(i.key()), qPrintable(i.value().toString()));
                }
          */
          }

    //---------------------------------------------------------
    //   measureDurationAsFraction
    //---------------------------------------------------------

    /**
     Determine a suitable measure duration value given the time signature
     by setting the duration denominator to be greater than or equal
     to the time signature denominator
     */

    static Fraction measureDurationAsFraction(const Fraction length, const int tsigtype)
          {
          if (tsigtype <= 0)
                // invalid tsigtype
                return length;

          Fraction res = length;
          while (res.denominator() < tsigtype) {
                res.setNumerator(res.numerator() * 2);
                res.setDenominator(res.denominator() * 2);
                }
          return res;
          }

    //---------------------------------------------------------
    //   measure
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure node:
     read the measures data as required to determine measure timing, octave shifts
     and assign voices and staves.
     */

    void MusicXMLParserPass1::measure(const QString& partId,
                                      const Fraction time,
                                      Fraction& mdur,
                                      VoiceOverlapDetector& vod)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "measure");
          logDebugTrace("MusicXMLParserPass1::measure");
          QString number = _e.attributes().value("number").toString();

          Fraction mTime; // current time stamp within measure
          Fraction mDura; // current total measure duration
          vod.newMeasure();

          while (_e.readNextStartElement()) {
                if (_e.name() == "attributes")
                      attributes(partId);
                else if (_e.name() == "note") {
                      Fraction dura;
                      // note: chord and grace note handling done in note()
                      note(partId, time + mTime, dura, vod);
                      if (dura.isValid()) {
                            mTime += dura;
                            if (mTime > mDura)
                                  mDura = mTime;
                            }
                      }
                else if (_e.name() == "forward") {
                      Fraction dura;
                      forward(dura);
                      if (dura.isValid()) {
                            mTime += dura;
                            if (mTime > mDura)
                                  mDura = mTime;
                            }
                      }
                else if (_e.name() == "backup") {
                      Fraction dura;
                      backup(dura);
                      if (dura.isValid()) {
                            if (dura <= mTime)
                                  mTime -= dura;
                            else {
                                  logError("backup beyond measure start");
                                  mTime.set(0, 1);
                                  }
                            }
                      }
                else if (_e.name() == "direction")
                      direction(partId, time + mTime);
                else
                      skipLogCurrElem();

                /*
                 qDebug("mTime %s (%s) mDura %s (%s)",
                 qPrintable(mTime.print()),
                 qPrintable(mTime.reduced().print()),
                 qPrintable(mDura.print()),
                 qPrintable(mDura.reduced().print()));
                 */
                }

          // debug vod
          // vod.dump();
          // copy overlap data from vod to voicelist
          copyOverlapData(vod, _parts[partId].voicelist);

          // measure duration fixups
          mDura.reduce();

          // fix for PDFtoMusic Pro v1.3.0d Build BF4E (which sometimes generates empty measures)
          // if no valid length found and length according to time signature is known,
          // use length according to time signature
          if (mDura.isZero() && _timeSigDura.isValid() && _timeSigDura > Fraction(0, 1))
                mDura = _timeSigDura;

          // if necessary, round up to an integral number of 1/64s,
          // to comply with MuseScores actual measure length constraints
          // TODO: calculate in fraction
          int length = mDura.ticks();
          int correctedLength = length;
          if ((length % (MScore::division/16)) != 0) {
                correctedLength = ((length / (MScore::division/16)) + 1) * (MScore::division/16);
                mDura = Fraction::fromTicks(correctedLength);
                }

          // set measure duration to a suitable value given the time signature
          if (_timeSigDura.isValid() && _timeSigDura > Fraction(0, 1)) {
                int btp = _timeSigDura.denominator();
                if (btp > 0)
                      mDura = measureDurationAsFraction(mDura, btp);
                }

          // set return value(s)
          mdur = mDura;

          // set measure number and duration
          /*
          qDebug("part %s measure %s dura %s (%d)",
                 qPrintable(partId), qPrintable(number), qPrintable(mdur.print()), mdur.ticks());
           */
          _parts[partId].addMeasureNumberAndDuration(number, mdur);
          }

    //---------------------------------------------------------
    //   attributes
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/attributes node.
     */

    void MusicXMLParserPass1::attributes(const QString& partId)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "attributes");
          logDebugTrace("MusicXMLParserPass1::attributes");

          while (_e.readNextStartElement()) {
                if (_e.name() == "clef")
                      clef(partId);
                else if (_e.name() == "divisions")
                      divisions();
                else if (_e.name() == "staff-details")
                      staffDetails(partId);
                else if (_e.name() == "staves")
                      staves(partId);
                else if (_e.name() == "time")
                      time();
                else
                      skipLogCurrElem();
                }
          }

    //---------------------------------------------------------
    //   clef
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/attributes/clef node.
     Set the staff type based on clef type
     TODO: check if staff type setting could be simplified
     */

    void MusicXMLParserPass1::clef(const QString& partId)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "clef");
          logDebugTrace("MusicXMLParserPass1::clef");

          QString number = _e.attributes().value("number").toString();
          int n = 0;
          if (number != "") {
                n = number.toInt();
                if (n <= 0) {
                      logError(QString("invalid number %1").arg(number));
                      n = 0;
                      }
                else
                      n--;              // make zero-based
                }

          StaffTypes staffType = StaffTypes::STANDARD;

          while (_e.readNextStartElement()) {
                if (_e.name() == "sign") {
                      QString sign = _e.readElementText();
                      if (sign == "TAB")
                            staffType = StaffTypes::TAB_DEFAULT;
                      else if (sign == "percussion")
                            staffType = StaffTypes::PERC_DEFAULT;
                      }
                else
                      skipLogCurrElem();
                }

          Part* part = getPart(partId);
          Q_ASSERT(part);
          int staves = part->nstaves();
          int staffIdx = _score->staffIdx(part);

          // TODO: changed for #55501, but now staff type init is shared between pass 1 and 2
          // old code: if (0 <= n && n < staves && staffType != StaffTypes::STANDARD)
          if (0 <= n && n < staves && staffType == StaffTypes::TAB_DEFAULT)
                _score->staff(staffIdx + n)->setStaffType(0, StaffType::preset(staffType));
          }

    //---------------------------------------------------------
    //   determineTimeSig
    //---------------------------------------------------------

    /**
     Determine the time signature based on \a beats, \a beatType and \a timeSymbol.
     Sets return parameters \a st, \a bts, \a btp.
     Return true if OK, false on error.
     */

    // TODO: share between pass 1 and pass 2

    static bool determineTimeSig(const QString beats, const QString beatType, const QString timeSymbol,
                                 TimeSigType& st, int& bts, int& btp)
          {
          // initialize
          st  = TimeSigType::NORMAL;
          bts = 0;             // the beats (max 4 separated by "+") as integer
          btp = 0;             // beat-type as integer
          // determine if timesig is valid
          if (beats == "2" && beatType == "2" && timeSymbol == "cut") {
                st = TimeSigType::ALLA_BREVE;
                bts = 2;
                btp = 2;
                return true;
                }
          else if (beats == "4" && beatType == "4" && timeSymbol == "common") {
                st = TimeSigType::FOUR_FOUR;
                bts = 4;
                btp = 4;
                return true;
                }
          else {
                if (!timeSymbol.isEmpty() && timeSymbol != "normal") {
                      qDebug("determineTimeSig: time symbol <%s> not recognized with beats=%s and beat-type=%s",
                             qPrintable(timeSymbol), qPrintable(beats), qPrintable(beatType));       // TODO
                      return false;
                      }

                btp = beatType.toInt();
                QStringList list = beats.split("+");
                for (int i = 0; i < list.size(); i++)
                      bts += list.at(i).toInt();
                }

          // determine if bts and btp are valid
          if (bts <= 0 || btp <=0) {
                qDebug("determineTimeSig: beats=%s and/or beat-type=%s not recognized",
                       qPrintable(beats), qPrintable(beatType));         // TODO
                return false;
                }

          return true;
          }

    //---------------------------------------------------------
    //   time
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/attributes/time node.
     */

    void MusicXMLParserPass1::time()
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "time");

          QString beats;
          QString beatType;
          QString timeSymbol = _e.attributes().value("symbol").toString();

          while (_e.readNextStartElement()) {
                if (_e.name() == "beats")
                      beats = _e.readElementText();
                else if (_e.name() == "beat-type")
                      beatType = _e.readElementText();
                else
                      skipLogCurrElem();
                }

          if (beats != "" && beatType != "") {
                // determine if timesig is valid
                TimeSigType st  = TimeSigType::NORMAL;
                int bts = 0;       // total beats as integer (beats may contain multiple numbers, separated by "+")
                int btp = 0;       // beat-type as integer
                if (determineTimeSig(beats, beatType, timeSymbol, st, bts, btp)) {
                      _timeSigDura = Fraction(bts, btp);
                      }
                }
          }

    //---------------------------------------------------------
    //   divisions
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/attributes/divisions node.
     */

    void MusicXMLParserPass1::divisions()
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "divisions");

          _divs = _e.readElementText().toInt();
          if (!(_divs > 0))
                logError("illegal divisions");
          }

    //---------------------------------------------------------
    //   setStaffLines
    //---------------------------------------------------------

    /**
     Set stafflines and barline span for a single staff
     */

    static void setStaffLines(Score* score, int staffIdx, int stafflines)
          {
          score->staff(staffIdx)->setLines(0, stafflines);
          score->staff(staffIdx)->setBarLineTo(0);  // default
          }

    //---------------------------------------------------------
    //   staffDetails
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/attributes/staff-details node.
     */

    void MusicXMLParserPass1::staffDetails(const QString& partId)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "staff-details");
          logDebugTrace("MusicXMLParserPass1::staffDetails");

          Part* part = getPart(partId);
          Q_ASSERT(part);
          int staves = part->nstaves();

          QString number = _e.attributes().value("number").toString();
          int n = 1; // default
          if (number != "") {
                n = number.toInt();
                if (n <= 0 || n > staves) {
                      logError(QString("invalid staff-details number %1").arg(number));
                      n = 1;
                      }
                }
          n--;        // make zero-based

          int staffIdx = _score->staffIdx(part) + n;

          StringData* t = 0;
          if (_score->staff(staffIdx)->isTabStaff(0)) {
                t = new StringData;
                t->setFrets(25);       // sensible default
                }

          int staffLines = 0;
          while (_e.readNextStartElement()) {
                if (_e.name() == "staff-lines") {
                      // save staff lines for later
                      staffLines = _e.readElementText().toInt();
                      // for a TAB staff also resize the string table and init with zeroes
                      if (t) {
                            if (0 < staffLines)
                                  t->stringList() = QVector<instrString>(staffLines).toList();
                            else
                                  logError(QString("illegal staff-lines %1").arg(staffLines));
                            }
                      }
                else if (_e.name() == "staff-tuning")
                      staffTuning(t);
                else
                      skipLogCurrElem();
                }

          if (staffLines > 0) {
                setStaffLines(_score, staffIdx, staffLines);
                }

          if (t) {
                Instrument* i = part->instrument();
                i->setStringData(*t);
                }
          }

    //---------------------------------------------------------
    //   MusicXMLStepAltOct2Pitch
    //---------------------------------------------------------

    /**
     Convert MusicXML \a step (0=C, 1=D, etc.) / \a alter / \a octave to midi pitch.
     Note: same code is in pass 1 and in pass 2.
     TODO: combine
     */

    static int MusicXMLStepAltOct2Pitch(int step, int alter, int octave)
          {
          //                       c  d  e  f  g  a   b
          static int table[7]  = { 0, 2, 4, 5, 7, 9, 11 };
          if (step < 0 || step > 6) {
                qDebug("MusicXMLStepAltOct2Pitch: illegal step %d", step);
                return -1;
                }
          int pitch = table[step] + alter + (octave+1) * 12;

          if (pitch < 0)
                pitch = -1;
          if (pitch > 127)
                pitch = -1;

          return pitch;
          }

    //---------------------------------------------------------
    //   staffTuning
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/attributes/staff-details/staff-tuning node.
     */

    void MusicXMLParserPass1::staffTuning(StringData* t)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "staff-tuning");
          logDebugTrace("MusicXMLParserPass1::staffTuning");

          // ignore <staff-tuning> if not a TAB staff
          if (!t) {
                logError("<staff-tuning> on non-TAB staff");
                skipLogCurrElem();
                return;
                }

          int line   = _e.attributes().value("line").toInt();
          int step   = 0;
          int alter  = 0;
          int octave = 0;
          while (_e.readNextStartElement()) {
                if (_e.name() == "tuning-alter")
                      alter = _e.readElementText().toInt();
                else if (_e.name() == "tuning-octave")
                      octave = _e.readElementText().toInt();
                else if (_e.name() == "tuning-step") {
                      QString strStep = _e.readElementText();
                      int pos = QString("CDEFGAB").indexOf(strStep);
                      if (strStep.size() == 1 && pos >=0 && pos < 7)
                            step = pos;
                      else
                            logError(QString("invalid step '%1'").arg(strStep));
                      }
                else
                      skipLogCurrElem();
                }

          if (0 < line && line <= t->stringList().size()) {
                int pitch = MusicXMLStepAltOct2Pitch(step, alter, octave);
                if (pitch >= 0)
                      t->stringList()[line - 1].pitch = pitch;
                else
                      logError(QString("invalid string %1 tuning step/alter/oct %2/%3/%4")
                               .arg(line).arg(step).arg(alter).arg(octave));
                }

          }

    //---------------------------------------------------------
    //   staves
    //---------------------------------------------------------

    /**
     Set number of staves for part \a partId to the max value of the current value
     and the value in the <staves> element.
     */

    void MusicXMLParserPass1::staves(const QString& partId)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "staves");
          logDebugTrace("MusicXMLParserPass1::staves");

          int staves = _e.readElementText().toInt();
          if (!(staves > 0 && staves <= MAX_STAVES)) {
                logError("illegal staves");
                return;
                }

          Part* part = _partMap.value(partId);
          Q_ASSERT(part);
          if (staves > part->nstaves())
                part->setStaves(staves);
          }

    //---------------------------------------------------------
    //   direction
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/direction node
     to be able to handle octave-shifts, as these must be interpreted
     in musical order instead of in MusicXML file order.
     */

    void MusicXMLParserPass1::direction(const QString& partId, const Fraction cTime)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "direction");

          // note: file order is direction-type first, then staff
          // this means staff is still unknown when direction-type is handled

          QList<MxmlOctaveShiftDesc> starts;
          QList<MxmlOctaveShiftDesc> stops;
          int staff = 0;

          while (_e.readNextStartElement()) {
                if (_e.name() == "direction-type")
                      directionType(cTime, starts, stops);
                else if (_e.name() == "staff") {
                      int nstaves = getPart(partId)->nstaves();
                      QString strStaff = _e.readElementText();
                      staff = strStaff.toInt() - 1;
                      if (0 <= staff && staff < nstaves)
                            ;  //qDebug("direction staff %d", staff + 1);
                      else {
                            logError(QString("invalid staff %1").arg(strStaff));
                            staff = 0;
                            }
                      }
                else
                      _e.skipCurrentElement();
                }

          // handle the stops first
          foreach (auto desc, stops) {
                if (_octaveShifts.contains(desc.num)) {
                      MxmlOctaveShiftDesc prevDesc = _octaveShifts.value(desc.num);
                      if (prevDesc.tp == MxmlOctaveShiftDesc::Type::UP
                          || prevDesc.tp == MxmlOctaveShiftDesc::Type::DOWN) {
                            // a complete pair
                            _parts[partId].addOctaveShift(staff, prevDesc.size, prevDesc.time);
                            _parts[partId].addOctaveShift(staff, -prevDesc.size, desc.time);
                            }
                      else
                            logError("double octave-shift stop");
                      _octaveShifts.remove(desc.num);
                      }
                else
                      _octaveShifts.insert(desc.num, desc);
                }

          // then handle the starts
          foreach (auto desc, starts) {
                if (_octaveShifts.contains(desc.num)) {
                      MxmlOctaveShiftDesc prevDesc = _octaveShifts.value(desc.num);
                      if (prevDesc.tp == MxmlOctaveShiftDesc::Type::STOP) {
                            // a complete pair
                            _parts[partId].addOctaveShift(staff, desc.size, desc.time);
                            _parts[partId].addOctaveShift(staff, -desc.size, prevDesc.time);
                            }
                      else
                            logError("double octave-shift start");
                      _octaveShifts.remove(desc.num);
                      }
                else
                      _octaveShifts.insert(desc.num, desc);
                }
          }

    //---------------------------------------------------------
    //   directionType
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/direction/direction-type node.
     */

    void MusicXMLParserPass1::directionType(const Fraction cTime,
                                            QList<MxmlOctaveShiftDesc>& starts,
                                            QList<MxmlOctaveShiftDesc>& stops)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "direction-type");

          while (_e.readNextStartElement()) {
                if (_e.name() == "octave-shift") {
                      QString number = _e.attributes().value("number").toString();
                      int n = 0;
                      if (number != "") {
                            n = number.toInt();
                            if (n <= 0)
                                  logError(QString("invalid number %1").arg(number));
                            else
                                  n--;  // make zero-based
                            }

                      if (0 <= n && n < MAX_NUMBER_LEVEL) {
                            short size = _e.attributes().value("size").toShort();
                            QString type = _e.attributes().value("type").toString();
                            //qDebug("octave-shift type '%s' size %d number %d", qPrintable(type), size, n);
                            MxmlOctaveShiftDesc osDesc;
                            handleOctaveShift(cTime, type, size, osDesc);
                            osDesc.num = n;
                            if (osDesc.tp == MxmlOctaveShiftDesc::Type::UP
                                || osDesc.tp == MxmlOctaveShiftDesc::Type::DOWN)
                                  starts.append(osDesc);
                            else if (osDesc.tp == MxmlOctaveShiftDesc::Type::STOP)
                                  stops.append(osDesc);
                            }
                      else {
                            logError(QString("invalid octave-shift number %1").arg(number));
                            }
                      _e.skipCurrentElement();
                      }
                else
                      _e.skipCurrentElement();
                }

          Q_ASSERT(_e.isEndElement() && _e.name() == "direction-type");
          }

    //---------------------------------------------------------
    //   handleOctaveShift
    //---------------------------------------------------------

    void MusicXMLParserPass1::handleOctaveShift(const Fraction cTime,
                                                const QString& type, short size,
                                                MxmlOctaveShiftDesc& desc)
          {
          MxmlOctaveShiftDesc::Type tp = MxmlOctaveShiftDesc::Type::NONE;
          short sz = 0;

          switch (size) {
                case   8: sz =  1; break;
                case  15: sz =  2; break;
                default:
                      logError(QString("invalid octave-shift size %1").arg(size));
                      return;
                }

          if (!cTime.isValid() || cTime < Fraction(0, 1))
                logError("invalid current time");

          if (type == "up")
                tp = MxmlOctaveShiftDesc::Type::UP;
          else if (type == "down") {
                tp = MxmlOctaveShiftDesc::Type::DOWN;
                sz *= -1;
                }
          else if (type == "stop")
                tp = MxmlOctaveShiftDesc::Type::STOP;
          else {
                logError(QString("invalid octave-shift type '%1'").arg(type));
                return;
                }

          desc = MxmlOctaveShiftDesc(tp, sz, cTime);
          }

    //---------------------------------------------------------
    //   setFirstInstr
    //---------------------------------------------------------

    void MusicXMLParserPass1::setFirstInstr(const QString& id, const Fraction stime)
          {
          // check for valid arguments
          if (id == "" || !stime.isValid() || stime < Fraction(0, 1))
                return;

          // check for no instrument found yet or new earliest start time
          // note: compare using <= to catch instrument at t=0
          if (_firstInstrId == "" || stime <= _firstInstrSTime) {
                _firstInstrId = id;
                _firstInstrSTime = stime;
                }
          }

    //---------------------------------------------------------
    //   note
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/note node.
     */

    void MusicXMLParserPass1::note(const QString& partId,
                                   const Fraction sTime,
                                   Fraction& dura,
                                   VoiceOverlapDetector& vod)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "note");
          //logDebugTrace("MusicXMLParserPass1::note");

          if (_e.attributes().value("print-spacing") == "no") {
                notePrintSpacingNo(dura);
                return;
                }

          //float alter = 0;
          bool chord = false;
          int dots = 0;
          bool grace = false;
          //int octave = -1;
          bool bRest = false;
          int staff = 1;
          //int step = 0;
          Fraction timeMod(1, 1);
          QString type;
          QString voice = "1";
          QString instrId;

          while (_e.readNextStartElement()) {
                if (_e.name() == "chord") {
                      chord = true;
                      _e.readNext();
                      }
                else if (_e.name() == "dot") {
                      dots++;
                      _e.readNext();
                      }
                else if (_e.name() == "duration")
                      duration(dura);
                else if (_e.name() == "grace") {
                      grace = true;
                      _e.readNext();
                      }
                else if (_e.name() == "instrument") {
                      instrId = _e.attributes().value("id").toString();
                      _e.readNext();
                      }
                else if (_e.name() == "pitch")
                      _e.skipCurrentElement();  // skip but don't log
                else if (_e.name() == "rest") {
                      bRest = true;
                      rest();
                      }
                else if (_e.name() == "staff") {
                      QString strStaff = _e.readElementText();
                      staff = strStaff.toInt();
                      // Bug fix for Cubase 6.5.5 which generates <staff>2</staff> in a single staff part
                      // Same fix is required in pass 1 and pass 2
                      Part* part = _partMap.value(partId);
                      Q_ASSERT(part);
                      if (staff <= 0 || staff > part->nstaves()) {
                            logError(QString("illegal staff '%1'").arg(strStaff));
                            staff = 1;
                            }
                      }
                else if (_e.name() == "time-modification")
                      timeModification(timeMod);
                else if (_e.name() == "type")
                      type = _e.readElementText();
                else if (_e.name() == "voice")
                      voice = _e.readElementText();
                else
                      skipLogCurrElem();
                }

          // convert staff to zero-based
          staff--;

          // multi-instrument handling
          setFirstInstr(instrId, sTime);
          QString prevInstrId = _parts[partId]._instrList.instrument(sTime);
          bool mustInsert = instrId != prevInstrId;
          /*
          qDebug("tick %s (%d) staff %d voice '%s' previnst='%s' instrument '%s' mustInsert %d",
                 qPrintable(sTime.print()),
                 sTime.ticks(),
                 staff + 1,
                 qPrintable(voice),
                 qPrintable(prevInstrId),
                 qPrintable(instrId),
                 mustInsert
                 );
          */
          if (mustInsert)
                _parts[partId]._instrList.setInstrument(instrId, sTime);

          // normalize duration
          if (dura.isValid())
                dura.reduce();

          // timing error check(s)
          QString errorStr;
          Fraction calcDura = calculateFraction(type, dots, timeMod);
          if (dura.isValid() && calcDura.isValid()) {
                if (dura != calcDura) {
                      errorStr = QString("calculated duration (%1) not equal to specified duration (%2)")
                            .arg(calcDura.print()).arg(dura.print());

                      if (bRest && type == "whole" && dura.isValid()) {
                            // Sibelius whole measure rest (not an error)
                            errorStr = "";
                            }
                      else if (grace && dura == Fraction(0, 1)) {
                            // grace note (not an error)
                            errorStr = "";
                            }
                      else {
                            const int maxDiff = 3; // maximum difference considered a rounding error
                            if (qAbs(calcDura.ticks() - dura.ticks()) <= maxDiff) {
                                  errorStr += " -> assuming rounding error";
                                  dura = calcDura;
                                  }
                            }

                      // Special case:
                      // Encore generates rests in tuplets w/o <tuplet> or <time-modification>.
                      // Detect this by comparing the actual duration with the expected duration
                      // based on note type. If actual is 2/3 of expected, the rest is part
                      // of a tuplet.
                      if (bRest && !timeMod.isValid()) {
                            if (2 * calcDura.ticks() == 3 * dura.ticks()) {
                                  timeMod = Fraction(2, 3);
                                  errorStr += " -> assuming triplet";
                                  }
                            }
                      }
                }
          else if (dura.isValid()) {
                // do not report an error for typeless (whole measure) rests
                if (!(bRest && type == ""))
                      errorStr = "calculated duration invalid, using specified duration";
                }
          else if (calcDura.isValid()) {
                if (!grace) {
                      errorStr = "specified duration invalid, using calculated duration";
                      dura = calcDura; // overrule dura
                      }
                }
          else {
                errorStr = "calculated and specified duration invalid, using 4/4";
                dura = Fraction(4, 4);
                }

          if (errorStr != "")
                logError(errorStr);

          // don't count chord or grace note duration
          // note that this does not check the MusicXML requirement that notes in a chord
          // cannot have a duration longer than the first note in the chord
          if (chord || grace)
                dura.set(0, 1);

          // store result
          if (dura.isValid() && dura > Fraction(0, 1)) {
                // count the chords
                if (!_parts.value(partId).voicelist.contains(voice)) {
                      VoiceDesc vs;
                      _parts[partId].voicelist.insert(voice, vs);
                      }
                _parts[partId].voicelist[voice].incrChordRests(staff);
                // determine note length for voice overlap detection
                // TODO
                vod.addNote(sTime.ticks(), (sTime + dura).ticks(), voice, staff);
                }
          }

    //---------------------------------------------------------
    //   notePrintSpacingNo
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/note node for a note with print-spacing="no".
     These are handled like a forward: only moving the time forward.
     */

    void MusicXMLParserPass1::notePrintSpacingNo(Fraction& dura)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "note");
          //logDebugTrace("MusicXMLParserPass1::notePrintSpacingNo");

          bool chord = false;
          bool grace = false;

          while (_e.readNextStartElement()) {
                if (_e.name() == "chord") {
                      chord = true;
                      _e.readNext();
                      }
                else if (_e.name() == "duration")
                      duration(dura);
                else if (_e.name() == "grace") {
                      grace = true;
                      _e.readNext();
                      }
                else
                      _e.skipCurrentElement();        // skip but don't log
                }

          // don't count chord or grace note duration
          // note that this does not check the MusicXML requirement that notes in a chord
          // cannot have a duration longer than the first note in the chord
          if (chord || grace)
                dura.set(0, 1);

          Q_ASSERT(_e.isEndElement() && _e.name() == "note");
          }

    //---------------------------------------------------------
    //   duration
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/note/duration node.
     */

    void MusicXMLParserPass1::duration(Fraction& dura)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "duration");
          //logDebugTrace("MusicXMLParserPass1::duration");

          dura.set(0, 0);  // invalid unless set correctly
          int intDura = _e.readElementText().toInt();
          if (intDura > 0) {
                if (_divs > 0)
                      dura.set(intDura, 4 * _divs);
                else
                      logError("illegal or uninitialized divisions");
                }
          else
                logError("illegal duration");
          //qDebug("duration %s valid %d", qPrintable(dura.print()), dura.isValid());
          }

    //---------------------------------------------------------
    //   forward
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/note/forward node.
     */

    void MusicXMLParserPass1::forward(Fraction& dura)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "forward");
          //logDebugTrace("MusicXMLParserPass1::forward");

          while (_e.readNextStartElement()) {
                if (_e.name() == "duration")
                      duration(dura);
                else if (_e.name() == "staff")
                      _e.skipCurrentElement();  // skip but don't log
                else if (_e.name() == "voice")
                      _e.skipCurrentElement();  // skip but don't log
                else
                      skipLogCurrElem();
                }
          }

    //---------------------------------------------------------
    //   backup
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/note/backup node.
     */

    void MusicXMLParserPass1::backup(Fraction& dura)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "backup");
          //logDebugTrace("MusicXMLParserPass1::backup");

          while (_e.readNextStartElement()) {
                if (_e.name() == "duration")
                      duration(dura);
                else
                      skipLogCurrElem();
                }
          }

    //---------------------------------------------------------
    //   timeModification
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/note/time-modification node.
     */

    void MusicXMLParserPass1::timeModification(Fraction& timeMod)
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "time-modification");
          //logDebugTrace("MusicXMLParserPass1::timeModification");

          int intActual = 0;
          int intNormal = 0;
          QString strActual;
          QString strNormal;

          while (_e.readNextStartElement()) {
                if (_e.name() == "actual-notes")
                      strActual = _e.readElementText();
                else if (_e.name() == "normal-notes")
                      strNormal = _e.readElementText();
                else
                      skipLogCurrElem();
                }

          intActual = strActual.toInt();
          intNormal = strNormal.toInt();
          if (intActual > 0 && intNormal > 0)
                timeMod.set(intNormal, intActual);
          else {
                timeMod.set(1, 1);
                logError(QString("illegal time-modification: actual-notes %1 normal-notes %2")
                         .arg(strActual).arg(strNormal));
                }
          }

    //---------------------------------------------------------
    //   rest
    //---------------------------------------------------------

    /**
     Parse the /score-partwise/part/measure/note/rest node.
     */

    void MusicXMLParserPass1::rest()
          {
          Q_ASSERT(_e.isStartElement() && _e.name() == "rest");
          //logDebugTrace("MusicXMLParserPass1::rest");

          while (_e.readNextStartElement()) {
                skipLogCurrElem();
                }
          }

    } // namespace Ms

↑ V668 There is no sense in testing the 'vbox' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error.

↑ V614 Uninitialized variable 'pageWidth' used. Consider checking the third actual argument of the 'doCredits' function.

↑ V614 Uninitialized variable 'pageHeight' used. Consider checking the fourth actual argument of the 'doCredits' function.

↑ V560 A part of conditional expression is always true: 0 <= n.