//=============================================================================
//  MusE Score
//  Linux Music Score Editor
//  $Id: importxml.cpp 5691 2013-02-19 09:47:24Z lasconic $
//
//  Copyright (C) 2002-2009 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.
//=============================================================================

/**
 MusicXML import.
 */

#include "config.h"
#include "mscore.h"
#include "musicxml.h"
#include "file.h"
#include "score.h"
#include "rest.h"
#include "chord.h"
#include "al/sig.h"
#include "key.h"
#include "clef.h"
#include "note.h"
#include "element.h"
#include "sym.h"
#include "slur.h"
#include "hairpin.h"
#include "tuplet.h"
#include "segment.h"
#include "dynamics.h"
#include "page.h"
#include "staff.h"
#include "part.h"
#include "measure.h"
#include "style.h"
#include "bracket.h"
#include "timesig.h"
#include "xml.h"
#include "barline.h"
#include "lyrics.h"
#include "volta.h"
#include "textline.h"
#include "keysig.h"
#include "pitchspelling.h"
#include "layoutbreak.h"
#include "tremolo.h"
#include "box.h"
#include "repeat.h"
#include "ottava.h"
#include "trill.h"
#include "pedal.h"
#include "unzip.h"
#include "harmony.h"
#include "tempotext.h"
#include "articulation.h"
#include "arpeggio.h"
#include "glissando.h"
#include "breath.h"
#include "al/tempo.h"
#include "musicxmlsupport.h"

//---------------------------------------------------------
//   xmlSetPitch
//---------------------------------------------------------

/**
 Convert MusicXML \a step / \a alter / \a octave to midi pitch,
 set pitch and tpc.
 */

static void xmlSetPitch(Note* n, char step, int alter, int octave, Ottava* ottava, int track)
      {
      // printf("xmlSetPitch(n=%p, st=%c, alter=%d, octave=%d)\n",
      //        n, step, alter, octave);
      int istep = step - 'A';
      //                       a  b   c  d  e  f  g
      static int table[7]  = { 9, 11, 0, 2, 4, 5, 7 };
      if (istep < 0 || istep > 6) {
            printf("xmlSetPitch: illegal pitch %d, <%c>\n", istep, step);
            return;
            }
      int pitch = table[istep] + alter + (octave+1) * 12;

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

      if (ottava != 0 && ottava->track() == track)
            pitch -= ottava->pitchShift();

      n->setPitch(pitch);

      //                        a  b  c  d  e  f  g
      static int table1[7]  = { 5, 6, 0, 1, 2, 3, 4 };
      int tpc  = step2tpc(table1[istep], alter);
      // alternativ: tpc = step2tpc((istep + 5) % 7, alter);      // rotate istep 5 steps
      n->setTpc(tpc);
      }


//---------------------------------------------------------
//   stringToInt
//---------------------------------------------------------

/**
 Convert a string in \a s into an int. Set *res to true iff conversion was
 successful. \a s may end with ".0", as is generated by Audiveris 3.2 and up,
 in elements <divisions>, <duration>, <alter> and <sound> attributes
 dynamics and tempo.
 In case of error val return a default value of 0. A negative number is an error.
 Note that non-integer values cannot be handled by mscore.
 */

static int stringToInt(const QString& s, bool* ok)
      {
      int res = 0;
      QString str = s;
      if (s.endsWith(".0"))
            str = s.left(s.size() - 2);
      res = str.toInt(ok);
      return res;
      }


//---------------------------------------------------------
//   calcTicks
//---------------------------------------------------------

static int calcTicks(QString text, int divisions)
      {
      bool ok;
      int val = stringToInt(text, &ok);
      if (!ok) {
            printf("MusicXml-Import: bad duration value: <%s>\n",
                   qPrintable(text));
            }
      if (val == 0)     // neuratron scanner produces sometimes 0 !?
            val = 1;
      val = val * AL::division / divisions;
      return val;
      }


//---------------------------------------------------------
//   moveTick
//---------------------------------------------------------

/**
 Move tick by amount specified in the element e, which must be
 a forward, backup or note.
 */

static void moveTick(int& tick, int& maxtick, int& lastLen, int divisions, QDomElement e)
      {
      if (e.tagName() == "forward") {
            for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                  if (ee.tagName() == "duration") {
                        int val = calcTicks(ee.text(), divisions);
                        // printf("forward %d\n", val);
                        tick += val;
                        if (tick > maxtick)
                              maxtick = tick;
                        lastLen = val;    // ?
                        }
                  else if (ee.tagName() == "voice")
                        ;
                  else if (ee.tagName() == "staff")
                        ;
                  else
                        domError(ee);
                  }
            }
      else if (e.tagName() == "backup") {
            for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                  if (ee.tagName() == "duration") {
                        int val = calcTicks(ee.text(), divisions);
                        // printf("backup %d\n", val);
                        tick -= val;
                        lastLen = val;    // ?
                        }
                  else
                        domError(ee);
                  }
            }
      else if (e.tagName() == "note") {
            bool grace   = false;
            int ticks = 0;
            for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                  QString tag(ee.tagName());
                  if (tag == "grace") {
                        grace = true;
                        }
                  else if (tag == "duration") {
                        ticks = calcTicks(ee.text(), divisions);
                        }
                  }
            if (!grace) {
                  lastLen = ticks; // ?
                  tick += ticks;
                  if (tick > maxtick)
                        maxtick = tick;
                  }
            }
      }


//---------------------------------------------------------
//   MusicXml
//---------------------------------------------------------

/**
 MusicXml constructor.
 */

MusicXml::MusicXml(QDomDocument* d)
      {
      doc = d;
      maxLyrics = 0;
      lastVolta = 0;
      beamMode = BEAM_NO;
      }

//---------------------------------------------------------
//   LoadMusicXml
//---------------------------------------------------------

/**
 Loader for MusicXml files.
 */

class LoadMusicXml : public LoadFile {
      QDomDocument* _doc;

public:
      LoadMusicXml()
            {
            _doc = new QDomDocument();
            }
      ~LoadMusicXml()
            {
            delete _doc;
            }
      virtual bool loader(QFile* f);
      QDomDocument* doc() const { return _doc; }
      };

//---------------------------------------------------------
//   loader
//---------------------------------------------------------

/**
 Load MusicXML file \a qf, return true if OK and false on error.
 */

bool LoadMusicXml::loader(QFile* qf)
      {
      int line, column;
      QString err;
      if (!_doc->setContent(qf, false, &err, &line, &column)) {
            QString col, ln;
            col.setNum(column);
            ln.setNum(line);
            error = err + "\n at line " + ln + " column " + col;
            return false;
            }
      docName = qf->fileName();
      return true;
      }

//---------------------------------------------------------
//   LoadCompressedMusicXml
//---------------------------------------------------------

/**
 Loader for compressed MusicXml files.
 */

class LoadCompressedMusicXml : public LoadFile {
      QDomDocument* _doc;

public:
      LoadCompressedMusicXml()
            {
            _doc = new QDomDocument();
            }
      ~LoadCompressedMusicXml()
            {
            delete _doc;
            }
      virtual bool loader(QFile* f);
      QDomDocument* doc() const { return _doc; }
      };

//---------------------------------------------------------
//   loader
//---------------------------------------------------------

/**
 Load compressed MusicXML file \a qf, return false if OK and true on error.
 */

bool LoadCompressedMusicXml::loader(QFile* qf)
      {
      UnZip uz;
      UnZip::ErrorCode ec;
      ec = uz.openArchive(qf->fileName());
      if (ec != UnZip::Ok) {
            error = "Unable to open archive(" + qf->fileName() + "):\n" + uz.formatError(ec);
            return false;
            }

      QBuffer cbuf;
      cbuf.open(QIODevice::WriteOnly);
      ec = uz.extractFile("META-INF/container.xml", &cbuf);

      QDomDocument container;
      int line, column;
      QString err;
      if (!container.setContent(cbuf.data(), false, &err, &line, &column)) {
            QString col, ln;
            col.setNum(column);
            ln.setNum(line);
            error = err + "\n at line " + ln + " column " + col;
            printf("error: %s\n", error.toLatin1().data());
            return false;
            }

      // extract first rootfile
      QString rootfile = "";
      for (QDomElement e = container.documentElement(); !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "container") {
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "rootfiles") {
                              for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                                    if (eee.tagName() == "rootfile") {
                                          if (rootfile == "")
                                                rootfile = eee.attribute(QString("full-path"));
                                          }
                                    else
                                          domError(eee);
                                    }
                              }
                        else
                              domError(ee);
                        }
                  }
            else
                  domError(e);
            }
      if (rootfile == "") {
            printf("can't find rootfile in: %s\n", qf->fileName().toLatin1().data());
            return false;
            }

      QBuffer dbuf;
      dbuf.open(QIODevice::WriteOnly);
      ec = uz.extractFile(rootfile, &dbuf);

      if (!_doc->setContent(dbuf.data(), false, &err, &line, &column)) {
            QString col, ln;
            col.setNum(column);
            ln.setNum(line);
            error = err + "\n at line " + ln + " column " + col;
            printf("error: %s\n", qPrintable(error));
            return false;
            }
      docName = qf->fileName();
      return true;
      }

//---------------------------------------------------------
//   importMusicXml
//---------------------------------------------------------

/**
 Import MusicXML file \a name into the Score.
 return false on error
 */

bool Score::importMusicXml(const QString& name)
      {
      LoadMusicXml lx;
      if (!lx.load(name))
            return false;
      setSaved(false);
      MusicXml musicxml(lx.doc());
      musicxml.import(this);
      connectTies();
      layoutAll = true;
      _created = false;
      return true;
      }

//---------------------------------------------------------
//   importCompressedMusicXml
//---------------------------------------------------------

/**
 Import compressed MusicXML file \a name into the Score.
 return false on error
 */

bool Score::importCompressedMusicXml(const QString& name)
      {
      LoadCompressedMusicXml lx;
      if (!lx.load(name))
            return false;
      setSaved(false);
      MusicXml musicxml(lx.doc());
      musicxml.import(this);
      connectTies();
      layoutAll = true;
      _created = false;
      return true;
      }

//---------------------------------------------------------
//   import
//      scorePartwise
//        part-list
//        part
//        work
//        identification
//---------------------------------------------------------

static void tupletAssert();

/**
 Parse the MusicXML file, which must be in score-partwise format.
 */

void MusicXml::import(Score* s)
      {
      tupletAssert();
      score  = s;
      tie    = 0;
      for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
            slur[i] = 0;
      for (int i = 0; i < MAX_BRACKETS; ++i)
            bracket[i] = 0;
      for (int i = 0; i < MAX_DASHES; ++i)
            dashes[i] = 0;
      ottava = 0;
      trill = 0;
      pedal = 0;
      tremStart = 0;

      // TODO enable only if multi-measure rests used
      // score->style().set(ST_createMultiMeasureRests, true);

      for (QDomElement e = doc->documentElement(); !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "score-partwise")
                  scorePartwise(e.firstChildElement());
            else
                  domError(e);
            }
      }

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

static Text* addText(VBox*& vbx, Score* s, QString strTxt, int sbtp, int stl)
      {
      if (!strTxt.isEmpty()) {
            Text* text = new Text(s);
            text->setSubtype(sbtp);
            text->setTextStyle(stl);
            text->setText(strTxt);
            if (vbx == 0)
                  vbx = new VBox(s);
            vbx->add(text);
            return text;
            }
      return 0;
      }

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

/**
 Create Text elements for the credits read from MusicXML credit-words elements.
 If no credits are found, create credits from meta data.
 */

void MusicXml::doCredits()
      {
      // printf("MusicXml::doCredits()\n");
      PageFormat* pf = score->pageFormat();
      //printf("page format w=%g h=%g spatium=%g DPMM=%g DPI=%g\n",
      //        pf->width(), pf->height(), score->spatium(), DPMM, DPI);
      // page width and height in tenths
      const double pw  = pf->width() * 10 * DPI / score->spatium();
      const double ph  = pf->height() * 10 * DPI / score->spatium();
      const double mgl  = pf->oddLeftMargin * 10 * DPI / score->spatium();
      const double mgt  = pf->oddTopMargin * 10 * DPI / score->spatium();
      const double mgb  = pf->oddBottomMargin * 10 * DPI / score->spatium();

      const int pw1 = (int) (pw / 3);
      const int pw2 = (int) (pw * 2 / 3);
      const int ph2 = (int) (ph / 2);
      // printf("page format w=%g h=%g\n", pw, ph);
      // printf("page format pw1=%d pw2=%d ph2=%d\n", pw1, pw2, ph2);
      // dump the credits

      /*for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
            CreditWords* w = *ci;
            printf("credit-words defx=%g defy=%g just=%s hal=%s val=%s words=%s\n",
                  w->defaultX,
                  w->defaultY,
                  w->justify.toUtf8().data(),
                  w->hAlign.toUtf8().data(),
                  w->vAlign.toUtf8().data(),
                  w->words.toUtf8().data());
            }*/

      // apply simple heuristics using only default x and y
      // to recognize the meaning of credit words
      CreditWords* crwTitle = 0;
      CreditWords* crwSubTitle = 0;
      CreditWords* crwComposer = 0;
      CreditWords* crwPoet = 0;
      CreditWords* crwCopyRight = 0;
      // title is highest above middle of the page that is in the middle column
      // it is done first because subtitle detection depends on the title
      for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
            CreditWords* w = *ci;
            double defx = w->defaultX;
            double defy = w->defaultY;
            if (defy > ph2 && pw1 < defx && defx < pw2) {
                  // found a possible title
                  if (!crwTitle || defy > crwTitle->defaultY) crwTitle = w;
                  }
            }
      // subtitle is highest above middle of the page that is
      // in the middle column and is not the title
      for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
            CreditWords* w = *ci;
            double defx = w->defaultX;
            double defy = w->defaultY;
            if (defy > ph2 && pw1 < defx && defx < pw2) {
                  // found a possible subtitle
                  if ((!crwSubTitle || defy > crwSubTitle->defaultY)
                      && w != crwTitle)
                        crwSubTitle = w;
                  }
            // composer is above middle of the page and in the right column
            if (defy > ph2 && pw2 < defx) {
                  // found composer
                  if (!crwComposer) crwComposer = w;
                  }
            // poet is above middle of the page and in the left column
            if (defy > ph2 && defx < pw1) {
                  // found poet
                  if (!crwPoet) crwPoet = w;
                  }
            // copyright is below (page height - margin bottom - 2 staves) of the page and in the middle column
            if ((ph - defy) > ( ph - mgb - 80) && pw1 < defx && defx < pw2) {
                  // found copyright
                  if (!crwCopyRight) crwCopyRight = w;
                  }
            } // end for (ciCreditWords ...
      /*
      if (crwTitle) printf("title='%s'\n", crwTitle->words.toUtf8().data());
      if (crwSubTitle) printf("subtitle='%s'\n", crwSubTitle->words.toUtf8().data());
      if (crwComposer) printf("composer='%s'\n", crwComposer->words.toUtf8().data());
      if (crwPoet) printf("poet='%s'\n", crwPoet->words.toUtf8().data());
      if (crwCopyRight) printf("copyright='%s'\n", crwCopyRight->words.toUtf8().data());
      */

      if (crwTitle || crwSubTitle || crwComposer || crwPoet || crwCopyRight)
            score->setCreditsRead(true);

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

      if (score->creditsRead()) {
            if (crwTitle) strTitle = crwTitle->words;
            if (crwSubTitle) strSubTitle = crwSubTitle->words;
            if (crwComposer) strComposer = crwComposer->words;
            if (crwPoet) strPoet = crwPoet->words;
            }
      else {
            if (!(score->movementTitle().isEmpty() && score->workTitle().isEmpty())) {
                  strTitle = score->movementTitle();
                  if (strTitle.isEmpty())
                        strTitle = score->workTitle();
                  }
            if (!(score->movementNumber().isEmpty() && score->workNumber().isEmpty())) {
                  strSubTitle = score->movementNumber();
                  if (strSubTitle.isEmpty())
                        strSubTitle = score->workNumber();
                  }
            if (!composer.isEmpty()) strComposer = composer;
            if (!poet.isEmpty()) strPoet = poet;
            if (!translator.isEmpty()) strTranslator = translator;
            }

      VBox* vbox  = 0;
      addText(vbox, score, strTitle, TEXT_TITLE, TEXT_STYLE_TITLE);
      addText(vbox, score, strSubTitle, TEXT_SUBTITLE, TEXT_STYLE_SUBTITLE);
      addText(vbox, score, strComposer, TEXT_COMPOSER, TEXT_STYLE_COMPOSER);
      addText(vbox, score, strPoet, TEXT_POET, TEXT_STYLE_POET);
      addText(vbox, score, strTranslator, TEXT_TRANSLATOR, TEXT_STYLE_TRANSLATOR);

      if (crwCopyRight) score->setCopyright(crwCopyRight->words);

      //deal with remaining credits on page 1
      //TODO deal with other pages...
      for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
            CreditWords* w = *ci;
            if (w != crwTitle && w != crwSubTitle && w != crwComposer
                && w != crwPoet && w != crwCopyRight) {
                  if (w->page == 1) {
                        Text* t = addText(vbox, score, w->words, TEXT_FRAME, TEXT_STYLE_FRAME);
                        if (t) {
                              //computation in tenths, get coordinates in the VBox system
                              double x = w->defaultX - mgl;
                              double y = ph - w->defaultY - mgt;
                              //change tenths to spatium
                              t->setUserOff(QPointF(x*0.1*score->spatium(), y*0.1*score->spatium()));
                              }
                        }
                  }
            }
      //add the vbox at top of page 1
      if (vbox) {
            vbox->setTick(0);
            score->measures()->add(vbox);
            }
      }

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

/**
 Determine the time signature based on \a beats, \a beatType and \a timeSymbol.
 Return the time signature subtype.
 Return 0 on error.
 */

static int determineTimeSig(const QString beats, const QString beatType, const QString timeSymbol = "")
      {
      // determine if timesig is valid
      int st = 0;        // timesig subtype calculated
      int bts[4];        // the beats (max 4 separated by "+") as integer
      int btp = 0;       // beat-type as integer
      if (beats == "2" && beatType == "2" && timeSymbol == "cut") {
            st = TSIG_ALLA_BREVE;
            }
      else if (beats == "4" && beatType == "4" && timeSymbol == "common") {
            st = TSIG_FOUR_FOUR;
            }
      else {
            if (!timeSymbol.isEmpty()) {
                  printf("ImportMusicXml: time symbol <%s> not recognized with beats=%s and beat-type=%s\n",
                         qPrintable(timeSymbol), qPrintable(beats), qPrintable(beatType));
                  }

            btp = beatType.toInt();
            QStringList list = beats.split("+");
            for (int i = 0; i < 4; i++) bts[i] = 0;
            for (int i = 0; i < list.size() && i < 4; i++) {
                  bts[i] = list.at(i).toInt();
                  }
            // the beat type and at least one beat must be non-zero
            if (btp && (bts[0] || bts[1] || bts[2] || bts[3])) {
                  TimeSig ts = TimeSig(0, btp, bts[0], bts[1], bts[2], bts[3]);
                  st = ts.subtype();
                  }
            }
      return st;
      }

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

/**
 Determine the length in ticks of each measure in part e.
 Return false on error.
 */

static bool determineMeasureLength(QDomElement e, QVector<int>& ml)
      {
      // debug
      printf("measurelength ml size %d\n", ml.size());
      int divisions = -1;
      int tick = 0;
      int maxtick = 0;
      int prevmaxtick = 0;
      int lastLen = 0;
      int measureNr = 0;
      bool result = true;
      QString beats = "";
      QString beatType = "";
      int timeSigLen = -1; // measure length in ticks according to the last timesig read

      for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
            // handle all measures in this part
            if (e.tagName() == "measure") {
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "attributes") {
                              for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                                    if (eee.tagName() == "divisions") {
                                          bool ok;
                                          divisions = stringToInt(eee.text(), &ok);
                                          if (!ok || divisions <= 0)
                                                printf("MusicXml-Import: bad divisions value: <%s>\n",
                                                       qPrintable(eee.text()));
                                          // debug
                                          printf("measurelength divisions %d\n", divisions);
                                          }
                                    else if (eee.tagName() == "time") {
                                          for (QDomElement eeee = eee.firstChildElement(); !eeee.isNull(); eeee = eeee.nextSiblingElement()) {
                                                if (eeee.tagName() == "beats")
                                                      beats = eeee.text();
                                                else if (eeee.tagName() == "beat-type") {
                                                      beatType = eeee.text();
                                                      }
                                                else if (eeee.tagName() == "senza-misura")
                                                      ;
                                                else
                                                      domError(eeee);
                                                }
                                          if (beats != "" && beatType != "") {
                                                int st = determineTimeSig(beats, beatType);
                                                // debug
                                                printf("measurelength beats %s beattype %s st %d",
                                                       qPrintable(beats), qPrintable(beatType), st);
                                                if (st) {
                                                      TimeSig ts = TimeSig(0, st);
                                                      timeSigLen = ts.getSig().ticks();
                                                      // debug
                                                      printf(" fraction %s len %d",
                                                             qPrintable(ts.getSig().print()), timeSigLen);
                                                      }
                                                printf("\n");
                                                }
                                          }
                                    }
                              }
                        // following tags can only be handled if duration is valid
                        if (divisions > 0) {
                              if (ee.tagName() == "note") {
                                    bool chord = false;
                                    bool grace = false;
                                    for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                                          if (eee.tagName() == "chord")
                                                chord = true;
                                          else if (eee.tagName() == "grace")
                                                grace = true;
                                          }
                                    if (chord && !grace)
                                          // LVIFIX: use of lastLen for chord handling is abit of a hack
                                          // TODO: replace by more elegant mechanism
                                          tick -= lastLen;
                                    moveTick(tick, maxtick, lastLen, divisions, ee);
                                    }
                              else if (ee.tagName() == "backup")
                                    moveTick(tick, maxtick, lastLen, divisions, ee);
                              else if (ee.tagName() == "forward")
                                    moveTick(tick, maxtick, lastLen, divisions, ee);
                              }
                        else
                              result = false;
                        }
                  // determine length of this measure
                  int length = maxtick - prevmaxtick;
                  // if necessary, round up to an integral number of 1/32s,
                  // to comply with MuseScores actual measure length constraints
                  int correctedLength = length;
                  if ((length % (AL::division/8)) != 0) {
                        correctedLength = ((length / (AL::division/8)) + 1) * (AL::division/8);
                        }
                  // 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 (correctedLength <= 0 && timeSigLen > 0)
                        correctedLength = timeSigLen;
                  // debug
                  printf("measurelength measure %d tick %d maxtick %d length %d corr length %d\n",
                         measureNr + 1, tick, maxtick, length, correctedLength);
                  length = correctedLength;
                  // store the maximum measure length
                  if (ml.size() < measureNr + 1)
                        // as we loop over the measures one by one
                        // if size of ml is too small, it will be one element short
                        ml.append(length);
                  else {
                        // check if measure contains more ticks in this part
                        // than in previous parts and if so update length
                        if (length > ml.at(measureNr))
                              ml[measureNr] = length;
                        }
                  // prepare for next measure
                  prevmaxtick = maxtick;
                  tick = maxtick;
                  measureNr++;
                  }
            }
      // debug
      printf("measurelength ml size %d res %d\n", ml.size(), result);
      for (int i = 0; i < ml.size(); i++)
            printf("measurelength ml[%d] %d\n", i + 1, ml.at(i));
      return result;
      }


//---------------------------------------------------------
//   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<int>& ml, QVector<int>& ms)
      {
      ms.resize(ml.size());
      // first measure starts at tick = 0
      ms[0] = 0;
      // all others start at start tick previous measure plus length previous measure
      for (int i = 1; i < ml.size(); i++)
            ms[i] = ms.at(i - 1) + ml.at(i - 1);
      // debug
      for (int i = 0; i < ms.size(); i++)
            printf("measurelength ms[%d] %d\n", i + 1, ms.at(i));
      }


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

/**
 Read the MusicXML score-partwise element.
 */

void MusicXml::scorePartwise(QDomElement ee)
      {
      // In a first pass collect all parts in case the part-list does not
      // list them all. Incomplete part-list's are generated by some versions
      // of Finale.
      // Furthermore, determine the length in ticks of each measure in the part
      for (QDomElement e = ee; !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "part") {
                  QString id = e.attribute(QString("id"));
                  if (id == "")
                        printf("MusicXML import: part without id\n");
                  else {
                        Part* part = new Part(score);
                        part->setId(id);
                        score->appendPart(part);
                        Staff* staff = new Staff(score, part, 0);
                        part->staves()->push_back(staff);
                        score->staves().push_back(staff);
                        tuplets.resize(VOICES); // part now contains one staff, thus VOICES voices
                        printf("measurelength part '%s'\n", qPrintable(id));
                        if (!determineMeasureLength(e, measureLength))
                              printf("MusicXML import: could not determine measure length for part '%s'\n",
                                     qPrintable(id));
                        }
                  }
            }

      // Determine the start tick of each measure in the part
      determineMeasureStart(measureLength, measureStart);

      // Read the score
      for (QDomElement e = ee; !e.isNull(); e = e.nextSiblingElement()) {
            QString tag(e.tagName());
            if (tag == "part-list")
                  xmlPartList(e.firstChildElement());
            else if (tag == "part")
                  xmlPart(e.firstChildElement(), e.attribute(QString("id")));
            else if (tag == "work") {
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "work-number")
                              score->setWorkNumber(ee.text());
                        else if (ee.tagName() == "work-title")
                              score->setWorkTitle(ee.text());
                        else
                              domError(ee);
                        }
                  }
            else if (tag == "identification") {
                  // TODO: this is metadata !
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "creator") {
                              // type is an arbitrary label
                              QString type = ee.attribute(QString("type"));
                              QString str = ee.text();
                              MusicXmlCreator* crt = new MusicXmlCreator(type, str);
                              score->addCreator(crt);
                              if (type == "composer")
                                    composer = str;
                              else if (type == "poet") //not in dtd ?
                                    poet = str;
                              else if (type == "lyricist")
                                    poet = str;
                              else if (type == "translator")
                                    translator = str;
                              else if (type == "transcriber")
                                    ;
                              else
                                    printf("unknown creator <%s>\n", type.toLatin1().data());
                              }
                        else if (ee.tagName() == "rights")
                              score->setmxmlRights(ee.text());
                        else if (ee.tagName() == "encoding")
                              domNotImplemented(ee);
                        else if (ee.tagName() == "source")
                              score->setSource(ee.text());
                        else if (ee.tagName() == "miscellaneous")
                              ;  // ignore
                        else
                              domError(ee);
                        }
                  }
            else if (tag == "defaults") {
                  double millimeter = score->spatium()/10.0;
                  double tenths = 1.0;
                  QDomElement pageLayoutElement;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        QString tag(ee.tagName());
                        if (tag == "scaling") {
                              for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                                    QString tag(eee.tagName());
                                    if (tag == "millimeters")
                                          millimeter = eee.text().toDouble();
                                    else if (tag == "tenths")
                                          tenths = eee.text().toDouble();
                                    else
                                          domError(eee);
                                    }
                              double _spatium = DPMM * (millimeter * 10.0 / tenths);
                              score->setSpatium(_spatium);
                              }
                        else if (tag == "page-layout") {
                              pageLayoutElement = ee;
                              }
                        else if (tag == "system-layout") {
                              for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                                    QString tag(eee.tagName());
                                    Spatium val(eee.text().toDouble() / 10.0);
                                    if (tag == "system-margins")
                                          ;
                                    else if (tag == "system-distance") {
                                          score->style().set(ST_systemDistance, val);
                                          }
                                    else if (tag == "top-system-distance")
                                          ;
                                    else
                                          domError(eee);
                                    }
                              }
                        else if (tag == "staff-layout") {
                              for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                                    QString tag(eee.tagName());
                                    Spatium val(eee.text().toDouble() / 10.0);
                                    if (tag == "staff-distance")
                                          score->style().set(ST_staffDistance, val);
                                    else
                                          domError(eee);
                                    }
                              }
                        else if (tag == "music-font")
                              domNotImplemented(ee);
                        else if (tag == "word-font")
                              domNotImplemented(ee);
                        else if (tag == "lyric-font")
                              domNotImplemented(ee);
                        else
                              domError(ee);
                        }

                  /* QMessageBox::warning(0,
                                       QWidget::tr("MuseScore: load XML"),
                                       QString("Val: ") + QString("%1").arg(QString::number(tenths,'f',2)) + " " + QString("%1").arg(QString::number(millimeter,'f',2)) + " " + QString("%1").arg(QString::number(INCH,'f',2)),
                                       QString::null, QWidget::tr("Quit"), QString::null, 0, 1);
                  */
                  score->pageFormat()->readMusicXML(pageLayoutElement, millimeter / (tenths * INCH) );
                  score->setDefaultsRead(true); // TODO only if actually succeeded ?
                  }
            else if (tag == "movement-number")
                  score->setMovementNumber(e.text());
            else if (tag == "movement-title")
                  score->setMovementTitle(e.text());
            else if (tag == "credit") {
                  int p = e.attribute(QString("page"), "1").toInt();
                  CreditWords* cw = 0;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        QString tag(ee.tagName());
                        if (tag == "credit-words") {
                              double defaultx    = ee.attribute(QString("default-x")).toDouble();
                              double defaulty    = ee.attribute(QString("default-y")).toDouble();
                              QString justify = ee.attribute(QString("justify"));
                              QString halign  = ee.attribute(QString("halign"));
                              QString valign  = ee.attribute(QString("valign"));
                              QString crwords = ee.text();
                              if (cw == 0) {
                                    cw = new CreditWords(p, defaultx, defaulty, justify, halign, valign, crwords);
                                    credits.append(cw);
                                    }
                              else {
                                    cw->words += ee.text();
                                    }
                              }
                        else
                              domError(ee);
                        }
                  }
            else
                  domError(e);
            }

      // add bracket where required
      const QList<Part*>* il = score->parts();
      // add bracket to multi-staff parts
      /* LVIFIX TODO is this necessary ?
      for (int idx = 0; idx < il->size(); ++idx) {
            Part* part = il->at(idx);
            printf("part %d staves=%d\n", idx, part->nstaves());
            if (part->nstaves() > 1)
                  part->staff(0)->addBracket(BracketItem(BRACKET_AKKOLADE, part->nstaves()));
            }
      */
      for (int i = 0; i < (int) partGroupList.size(); i++) {
            MusicXmlPartGroup* pg = partGroupList[i];
            // determine span in staves
            int stavesSpan = 0;
            for (int j = 0; j < pg->span; j++)
                  stavesSpan += il->at(pg->start + j)->nstaves();
            // and add bracket
            il->at(pg->start)->staff(0)->addBracket(BracketItem(pg->type, stavesSpan));
            if (pg->barlineSpan)
                  il->at(pg->start)->staff(0)->setBarLineSpan(pg->span);
            }
      }



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

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

static void partGroupStart(MusicXmlPartGroup* (&pgs)[MAX_PART_GROUPS], int n, int p, QString s, bool barlineSpan)
      {
      // printf("partGroupStart number=%d part=%d symbol=%s\n", n, p, s.toLatin1().data());
      if (n < 0 || n >= MAX_PART_GROUPS) {
            printf("illegal part-group number: %d\n", n);
            return;
            }

      if (pgs[n]) {
            printf("part-group number=%d already active\n", n);
            return;
            }

      int bracketType = NO_BRACKET;
      if (s == "")
            ;  //ignore
      else if (s == "brace")
            bracketType = BRACKET_AKKOLADE;
      else if (s == "bracket")
            bracketType = BRACKET_NORMAL;
      else if (s == "line")
            bracketType = BRACKET_NORMAL;
      else if (s == "square")
            bracketType = BRACKET_NORMAL;
      else {
            printf("part-group symbol=%s not supported\n", s.toLatin1().data());
            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(MusicXmlPartGroup* (&pgs)[MAX_PART_GROUPS], int n, int p,
                          std::vector<MusicXmlPartGroup*>& pgl)
      {
      // printf("partGroupStop number=%d part=%d\n", n, p);
      if (n < 0 || n >= MAX_PART_GROUPS) {
            printf("illegal part-group number: %d\n", n);
            return;
            }

      if (!pgs[n]) {
            printf("part-group number=%d not active\n", n);
            return;
            }

      pgs[n]->span = p - pgs[n]->start;
      // printf("part-group number=%d start=%d span=%d type=%d\n",
      //        n, pgs[n]->start, pgs[n]->span, pgs[n]->type);
      pgl.push_back(pgs[n]);
      pgs[n] = 0;
      }

//---------------------------------------------------------
//   xmlPartList
//---------------------------------------------------------

/**
 Read the MusicXML part-list element.
 */

void MusicXml::xmlPartList(QDomElement e)
      {
      int scoreParts = 0;
      bool barlineSpan = false;
      MusicXmlPartGroup* partGroups[MAX_PART_GROUPS];
      for (int i = 0; i < MAX_PART_GROUPS; ++i)
            partGroups[i] = 0;

      for (; !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "score-part")
                  xmlScorePart(e.firstChildElement(), e.attribute(QString("id")), scoreParts);
            else if (e.tagName() == "part-group") {
                  int number = e.attribute(QString("number")).toInt() - 1;
                  QString symbol = "";
                  QString type = e.attribute(QString("type"));
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "group-symbol")
                              symbol = ee.text();
                        else if (ee.tagName() == "group-barline") {
                              if (ee.text() == "yes")
                                    barlineSpan = true;
                              }
                        else
                              domError(ee);
                        }
                  if (type == "start")
                        partGroupStart(partGroups, number, scoreParts, symbol, barlineSpan);
                  else if (type == "stop")
                        partGroupStop(partGroups, number, scoreParts, partGroupList);
                  else
                        printf("Import MusicXml:xmlPartList: part-group type '%s' not supported\n",
                               type.toLatin1().data());
                  }
            else
                  domError(e);
            }
      }

//---------------------------------------------------------
//   xmlScorePart
//---------------------------------------------------------

/**
 Read the MusicXML score-part element.
 */

void MusicXml::xmlScorePart(QDomElement e, QString id, int& parts)
      {
      Part* part = 0;
      foreach(Part* p, *score->parts()) {
            if (p->id() == id) {
                  part = p;
                  parts++;
                  break;
                  }
            }
      if (part == 0) {
            // Some versions of Rosegarden (at least v11.02) mention parts
            // in the <part-list>, but don't contain the corresponding <part>s.
            // (i.e. the part-list is overcomplete).
            // These parts are reported but can safely be ignored.
            printf("Import MusicXml:xmlScorePart: cannot find part %s\n", qPrintable(id));
            return;
            }

      // printf("create track id:<%s>\n", id.toLatin1().data());

      for (; !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "part-name") {
                  // OK? (ws) Yes it should be ok.part-name is display in front of staff in finale. (la)
                  part->setLongName(e.text());
                  part->setTrackName(e.text());
                  }
            else if (e.tagName() == "part-abbreviation") {
                  part->setShortName(e.text());
                  }
            else if (e.tagName() == "score-instrument") {
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "instrument-name") {
                              // part-name or instrument-name?
                              if (part->longName()->getText().isEmpty())
                                    part->setLongName(ee.text());
                              }
                        else
                              domError(ee);
                        }
                  }
            else if (e.tagName() == "midi-instrument") {
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "midi-channel")
                              part->setMidiChannel(ee.text().toInt() - 1);
                        else if (ee.tagName() == "midi-program")
                              part->setMidiProgram(ee.text().toInt() - 1);
                        else if (ee.tagName() == "volume") {
                              double vol = ee.text().toDouble();
                              if (vol >= 0 && vol <= 100)
                                    part->setVolume(( vol / 100) * 127);
                              }
                        else if (ee.tagName() == "pan") {
                              double pan = ee.text().toDouble();
                              if (pan >= -90 && pan <= 90)
                                    part->setPan( ((pan + 90) / 180) * 127 );
                              }
                        else
                              domError(ee);
                        }
                  }
            else
                  domError(e);
            }
      /*
      score->parts()->push_back(part);
      Staff* staff = new Staff(score, part, 0);
      part->staves()->push_back(staff);
      score->staves().push_back(staff);
      */
      }


//---------------------------------------------------------
//   VoiceDesc
//---------------------------------------------------------

VoiceDesc::VoiceDesc() : _staff(-1), _voice(-1), _overlaps(false)
      {
      for (int i = 0; i < MAX_STAVES; ++i) {
            _chordRests[i] =  0;
            _staffAlloc[i] = -1;
            _voices[i]     = -1;
            }
      }

void VoiceDesc::incrChordRests(int s)
      {
      if (0 <= s && s < MAX_STAVES)
            _chordRests[s]++;
      }

int VoiceDesc::numberChordRests() const
      {
      int res = 0;
      for (int i = 0; i < MAX_STAVES; ++i)
            res += _chordRests[i];
      return res;
      }

int VoiceDesc::preferredStaff() const
      {
      int max = 0;
      int res = 0;
      for (int i = 0; i < MAX_STAVES; ++i)
            if (_chordRests[i] > max) {
                  max = _chordRests[i];
                  res = i;
                  }
      return res;
      }

QString VoiceDesc::toString() const
      {
      QString res = "[";
      for (int i = 0; i < MAX_STAVES; ++i)
            res += QString(" %1").arg(_chordRests[i]);
      res += QString(" ] overlaps %1").arg(_overlaps);
      if (_overlaps) {
            res += " staffAlloc [";
            for (int i = 0; i < MAX_STAVES; ++i)
                  res += QString(" %1").arg(_staffAlloc[i]);
            res += " ] voices [";
            for (int i = 0; i < MAX_STAVES; ++i)
                  res += QString(" %1").arg(_voices[i]);
            res += " ]";
            }
      else
            res += QString(" staff %1 voice %2").arg(_staff + 1).arg(_voice + 1);
      return res;
      }


// 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(QMap<int, VoiceDesc>& voicelist)
      {
      // 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 voicelist.size() times, as each inner loop handles exactly one item
      for (int i = 0; i < voicelist.size(); ++i) {
            // find the regular voice containing the highest number of chords and rests that has not been handled yet
            int max = 0;
            int key = -1;
            for (QMap<int, VoiceDesc>::const_iterator j = voicelist.constBegin(); j != voicelist.constEnd(); ++j) {
                  if (!j.value().overlaps() && j.value().numberChordRests() > max && j.value().staff() == -1) {
                        max = j.value().numberChordRests();
                        key = j.key();
                        }
                  }
            if (key >= 0) {
                  int prefSt = voicelist.value(key).preferredStaff();
                  if (voicesAllocated[prefSt] < VOICES) {
                        voicelist[key].setStaff(prefSt);
                        voicesAllocated[prefSt]++;
                        }
                  else
                        // out of voices: mark as used but not allocated
                        voicelist[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 voicelist.size() times, as each inner loop handles exactly one item
            for (int i = 0; i < voicelist.size(); ++i) {
                  // find the overlapping voice containing the highest number of chords and rests that has not been handled yet
                  int max = 0;
                  int key = -1;
                  for (QMap<int, VoiceDesc>::const_iterator j = voicelist.constBegin(); j != voicelist.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 >= 0) {
                        int prefSt = h;
                        if (voicesAllocated[prefSt] < VOICES) {
                              voicelist[key].setStaffAlloc(prefSt, 1);
                              voicesAllocated[prefSt]++;
                              }
                        else
                              // out of voices: mark as used but not allocated
                              voicelist[key].setStaffAlloc(prefSt, -2);
                        }
                  }
            }
      }


// 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(QMap<int, VoiceDesc>& voicelist)
      {
      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 (QMap<int, VoiceDesc>::const_iterator i = voicelist.constBegin(); i != voicelist.constEnd(); ++i) {
            int staff = i.value().staff();
            int key   = i.key();
            if (staff >= 0) {
                  voicelist[key].setVoice(nextVoice[staff]);
                  nextVoice[staff]++;
                  }
            }
      // handle overlapping voices
      // each voice may be in every staff
      for (QMap<int, VoiceDesc>::const_iterator i = voicelist.constBegin(); i != voicelist.constEnd(); ++i) {
            for (int j = 0; j < MAX_STAVES; ++j) {
                  int staffAlloc = i.value().staffAlloc(j);
                  int key   = i.key();
                  if (staffAlloc >= 0) {
                        voicelist[key].setVoice(j, nextVoice[j]);
                        nextVoice[j]++;
                        }
                  }
            }
      }

//  copy the overlap data from the overlap detector to the voice list

static void copyOverlapData(VoiceOverlapDetector& vod, QMap<int, VoiceDesc>& voicelist)
      {
      // printf("copyOverlapData voices found:");
      for (QMap<int, VoiceDesc>::const_iterator i = voicelist.constBegin(); i != voicelist.constEnd(); ++i) {
            int key = i.key();
            // printf(" %d", key + 1);
            if (vod.stavesOverlap(key))
                  voicelist[key].setOverlap(true);
            }
      // printf("\n");
      }

//---------------------------------------------------------
//   initVoiceMapperAndMapVoices
//   in: e is the parts first child node
//---------------------------------------------------------

/**
 Setup the voice mapper for a MusicXML part.
 */

void MusicXml::initVoiceMapperAndMapVoices(QDomElement e)
      {
      VoiceOverlapDetector vod;
      int loc_divisions = -1;
      int loc_tick      =  0;
      int loc_maxtick   =  0;
      int loc_lastLen   =  0;
      // init
      voicelist.clear();
      // count number of chordrests on each MusicXML staff
      for (; !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "measure") {
                  vod.newMeasure();
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "attributes") {
                              for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                                    if (eee.tagName() == "divisions") {
                                          bool ok;
                                          loc_divisions = stringToInt(eee.text(), &ok);
                                          if (!ok || loc_divisions <= 0)
                                                printf("MusicXml-Import: bad divisions value: <%s>\n",
                                                       qPrintable(eee.text()));
                                          // debug
                                          printf("measurelength loc_divisions %d\n", loc_divisions);
                                          }
                                    }
                              }
                        // following tags can only be handled if duration is valid
                        if (loc_divisions > 0) {
                              if (ee.tagName() == "note") {
                                    bool chord = false;
                                    bool grace = false;
                                    int voice = -1;
                                    int staff = -1;
                                    for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                                          QString tag(eee.tagName());
                                          QString s(eee.text());
                                          if (tag == "chord")
                                                chord = true;
                                          if (tag == "grace")
                                                grace = true;
                                          else if (tag == "voice")
                                                voice = s.toInt() - 1;
                                          else if (tag == "staff")
                                                staff = s.toInt() - 1;
                                          }
                                    // set correct defaults for missing elements
                                    if (voice == -1) voice = 0;
                                    if (staff == -1) staff = 0;
                                    if (!chord) {
                                          // count the chords (only the first note in a chord is counted)
                                          if (0 <= staff && staff < MAX_STAVES) {
                                                if (!voicelist.contains(voice)) {
                                                      VoiceDesc vs;
                                                      voicelist.insert(voice, vs);
                                                      }
                                                voicelist[voice].incrChordRests(staff);
                                                }
                                          // determine note length for voice oberlap detection
                                          if (!grace) {
                                                int startTick = loc_tick; // start tick for the last note
                                                moveTick(loc_tick, loc_maxtick, loc_lastLen, loc_divisions, ee);
                                                // printf(" endtick %d\n", tick);
                                                vod.addNote(startTick, loc_tick, voice, staff);
                                                }
                                          }
                                    }
                              else if (ee.tagName() == "backup")
                                    moveTick(loc_tick, loc_maxtick, loc_lastLen, loc_divisions, ee);
                              else if (ee.tagName() == "forward")
                                    moveTick(loc_tick, loc_maxtick, loc_lastLen, loc_divisions, ee);
                              }
                        }
                  // debug vod
                  // vod.dump();
                  // copy overlap data from vod to voicelist
                  copyOverlapData(vod, voicelist);
                  } // e.tagName() == "measure"
            }

      // allocate MuseScore staff to MusicXML voices
      allocateStaves(voicelist);
      // allocate MuseScore voice to MusicXML voices
      allocateVoices(voicelist);

      // debug: print results
      printf("voiceMapperStats: new staff\n");
      for (QMap<int, VoiceDesc>::const_iterator i = voicelist.constBegin(); i != voicelist.constEnd(); ++i) {
            printf("voiceMapperStats: voice %d staff count %s \n",
                   i.key() + 1, qPrintable(i.value().toString()));
            }
      }

//---------------------------------------------------------
//   fillGap -- fill one gap (tstart - tend) in this track in this measure with rest(s)
//---------------------------------------------------------

static void fillGap(Measure* measure, int track, int tstart, int tend)
      {
      int ctick = tstart;
      int restLen = tend - tstart;
      // note: as AL::division (#ticks in a quarter note) equals 480
      // AL::division / 64 (#ticks in a 256th note) uequals 7.5 but is rounded down to 7
      while (restLen > AL::division / 64) {
            int len = restLen;
            Duration d;
            if (measure->tickLen() == restLen)
                  d.setType(Duration::V_MEASURE);
            else
                  d.setVal(len);
            Rest* rest = new Rest(measure->score(), tstart, d);
            rest->setTrack(track);
            rest->setVisible(false);
            Segment* s = measure->getSegment(rest);
            s->add(rest);
            len = rest->tickLen();
            ctick   += len;
            restLen -= len;
            }
      }

//---------------------------------------------------------
//   fillGapsInFirstVoices -- fill gaps in first voice of every staff in this measure for this part with rest(s)
//---------------------------------------------------------

static void fillGapsInFirstVoices(Measure* measure, Part* part)
      {
      int measTick     = measure->tick();
      int measLen      = measure->tickLen();
      int nextMeasTick = measTick + measLen;
      int staffIdx = part->score()->staffIdx(part);
      for (int st = 0; st < part->nstaves(); ++st) {
            int track = (staffIdx + st) * VOICES;
            int endOfLastCR = measTick;
            for (Segment* s = measure->first(); s; s = s->next()) {
                  Element* el = s->element(track);
                  if (el) {
                        if (s->isChordRest()) {
                              ChordRest* cr  = static_cast<ChordRest*>(el);
                              int crTick     = cr->tick();
                              int crLen      = cr->tickLen();
                              int nextCrTick = crTick + crLen;
                              if (crTick > endOfLastCR)
                                    fillGap(measure, track, endOfLastCR, crTick);
                              endOfLastCR = nextCrTick;
                              }
                        }
                  }
            if (nextMeasTick > endOfLastCR)
                  fillGap(measure, track, endOfLastCR, nextMeasTick);
            }
      }

//---------------------------------------------------------
//   xmlPart
//---------------------------------------------------------

/**
 Read the MusicXML part element.
 */

void MusicXml::xmlPart(QDomElement e, QString id)
      {
      printf("xmlPart(id='%s')\n", qPrintable(id));
      if (id == "") {
            printf("MusicXML import: part without id\n");
            return;
            }
      Part* part = 0;
      foreach(Part* p, *score->parts()) {
            if (p->id() == id) {
                  part = p;
                  break;
                  }
            }
      if (part == 0) {
            printf("Import MusicXml:xmlPart: cannot find part %s\n", id.toLatin1().data());
            return;
            }
      tick                  = 0;
      maxtick               = 0;
      lastMeasureLen        = 0;
      multiMeasureRestCount = -1;
      startMultiMeasureRest = false;

      initVoiceMapperAndMapVoices(e);

      if (!score->measures()->first()) {
            doCredits();
            }

      for (int measureNr = 0; !e.isNull(); e = e.nextSiblingElement(), measureNr++) {
            if (e.tagName() == "measure") {
                  // set the correct start tick for the measure
                  tick = measureStart.at(measureNr);
                  Measure* measure = xmlMeasure(part, e, e.attribute(QString("number")).toInt()-1, measureLength.at(measureNr));
                  if (measure)
                        fillGapsInFirstVoices(measure, part);
                  }
            else
                  domError(e);
            }
      }


//---------------------------------------------------------
//   xmlMeasure
//---------------------------------------------------------

/**
 Read the MusicXML measure element.
 */

Measure* MusicXml::xmlMeasure(Part* part, QDomElement e, int number, int measureLen)
      {
      // printf("xmlMeasure %d begin\n", number);
      int staves = score->nstaves();
      int staff = score->staffIdx(part);

      if (staves == 0) {
            printf("MusicXml::xmlMeasure no staves!\n");
            return 0;
            }

      // search measure for tick
      Measure* measure = 0;
      Measure* lastMeasure = 0;
      for (MeasureBase* mb = score->measures()->first(); mb; mb = mb->next()) {
            if (mb->type() != MEASURE)
                  continue;
            Measure* m = (Measure*)mb;
            lastMeasure = m;
            if (m->tick() == tick) {
                  measure = m;
                  break;
                  }
            }
      if (!measure) {
            //
            // DEBUG:
            if (lastMeasure && lastMeasure->tick() > tick) {
                  printf("Measure at position %d not found!\n", tick);
                  }
            measure  = new Measure(score);
            measure->setTick(tick);
            measure->setNo(number);
            score->measures()->add(measure);
            }
      else {
            int pstaves = part->nstaves();
            for (int i = 0; i < pstaves; ++i) {
                  Staff* reals = score->staff(staff+i);
                  measure->mstaff(staff+i)->lines->setLines(reals->lines());
                  }
            }

      // must remember volta to handle <ending type="discontinue">
      //      Volta* lastVolta = 0;       // ws: move to global to allow for voltas spanning more
      //                                  //     than one measure

      QString implicit = e.attribute("implicit", "no");
      if (implicit == "yes")
            measure->setIrregular(true);


      for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "attributes")
                  xmlAttributes(measure, staff, e.firstChildElement());
            else if (e.tagName() == "note") {
                  xmlNote(measure, staff, e);
                  moveTick(tick, maxtick, lastLen, divisions, e);
                  // printf(" after inserting note tick=%d\n", tick);
                  }
            else if (e.tagName() == "backup") {
                  moveTick(tick, maxtick, lastLen, divisions, e);
                  }
            else if (e.tagName() == "direction") {
                  direction(measure, staff, e);
                  }
            else if (e.tagName() == "print") {
                  QString newSystem = e.attribute("new-system", "no");
                  QString newPage   = e.attribute("new-page", "no");
                  //
                  // in MScore the break happens _after_ the marked measure:
                  //
                  Measure* pm = (Measure*)(measure->prev());      // TODO: MeasureBase
                  if (pm == 0)
                        printf("ImportXml: warning: break on first measure\n");
                  else {
                        if (newSystem == "yes" || newPage == "yes") {
                              LayoutBreak* lb = new LayoutBreak(score);
                              lb->setTrack(staff * VOICES);
                              lb->setSubtype(
                                    newSystem == "yes" ? LAYOUT_BREAK_LINE : LAYOUT_BREAK_PAGE
                                    );
                              pm->add(lb);
                              }
                        }
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "system-layout") {
                              }
                        else if (ee.tagName() == "staff-layout") {
                              }
                        else
                              domError(ee);
                        }
                  }
            else if (e.tagName() == "forward") {
                  moveTick(tick, maxtick, lastLen, divisions, e);
                  }
            else if (e.tagName() == "barline") {
                  QString loc = e.attribute("location", "right");
                  QString barStyle;
                  QString endingNumber;
                  QString endingType;
                  QString endingText;
                  QString repeat;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "bar-style")
                              barStyle = ee.text();
                        else if (ee.tagName() == "ending") {
                              endingNumber = ee.attribute("number");
                              endingType   = ee.attribute("type");
                              endingText = ee.text();
                              }
                        else if (ee.tagName() == "repeat")
                              repeat = ee.attribute("direction");
                        else
                              domError(ee);
                        }
                  if ((barStyle != "") || (repeat != "")) {
                        BarLine* barLine = new BarLine(score);
                        bool visible = true;
                        if (barStyle == "light-heavy" && repeat == "backward") {
                              barLine->setSubtype(END_REPEAT);
                              }
                        else if (barStyle == "heavy-light" && repeat == "forward") {
                              barLine->setSubtype(START_REPEAT);
                              }
                        else if (barStyle == "light-heavy" && repeat.isEmpty())
                              barLine->setSubtype(END_BAR);
                        else if (barStyle == "regular")
                              barLine->setSubtype(NORMAL_BAR);
                        else if (barStyle == "dotted")
                              barLine->setSubtype(BROKEN_BAR);
                        else if (barStyle == "light-light")
                              barLine->setSubtype(DOUBLE_BAR);
                        /*
                        else if (barStyle == "heavy-light")
                              ;
                        else if (barStyle == "heavy-heavy")
                              ;
                        */
                        else if (barStyle == "none") {
                              barLine->setSubtype(NORMAL_BAR);
                              visible = false;
                              }
                        else if (barStyle == "") {
                              if (repeat == "backward")
                                    barLine->setSubtype(END_REPEAT);
                              else if (repeat == "forward")
                                    barLine->setSubtype(START_REPEAT);
                              else
                                    printf("ImportXml: warning: empty bar type\n");
                              }
                        else
                              printf("unsupported bar type <%s>\n", barStyle.toLatin1().data());
                        barLine->setTrack(staff * VOICES);
                        if (barLine->subtype() == START_REPEAT) {
                              measure->setRepeatFlags(measure->repeatFlags() | RepeatStart);
                              }
                        else if (barLine->subtype() == END_REPEAT) {
                              measure->setRepeatFlags(measure->repeatFlags() | RepeatEnd);
                              }
                        else {
                              if (loc == "right")
                                    measure->setEndBarLineType(barLine->subtype(), false, visible);
                              else if (measure->prevMeasure())
                                    measure->prevMeasure()->setEndBarLineType(barLine->subtype(), false, visible);
                              }
                        }
                  if (!(endingNumber.isEmpty() && endingType.isEmpty())) {
                        if (endingNumber.isEmpty())
                              printf("ImportXml: warning: empty ending number\n");
                        else if (endingType.isEmpty())
                              printf("ImportXml: warning: empty ending type\n");
                        else {
                              QStringList sl = endingNumber.split(",", QString::SkipEmptyParts);
                              QList<int> iEndingNumbers;
                              bool unsupported = false;
                              foreach(const QString &s, sl) {
                                    int iEndingNumber = s.toInt();
                                    if (iEndingNumber <= 0) {
                                          unsupported = true;
                                          break;
                                          }
                                    iEndingNumbers.append(iEndingNumber);
                                    }

                              if (unsupported)
                                    printf("ImportXml: warning: unsupported ending number <%s>\n",
                                           endingNumber.toLatin1().data());
                              else {
                                    if (endingType == "start") {
                                          Volta* volta = new Volta(score);
                                          volta->setTrack(staff * VOICES);
                                          volta->setTick(tick);
                                          volta->setText(endingText.isEmpty() ? endingNumber : endingText);
                                          // LVIFIX TODO also support endings "1 - 3"
                                          volta->endings().clear();
                                          volta->endings().append(iEndingNumbers);
                                          lastVolta = volta;
                                          }
                                    else if (endingType == "stop") {
                                          if (lastVolta) {
                                                lastVolta->setTick2(tick);
                                                lastVolta->setSubtype(Volta::VOLTA_CLOSED);
                                                score->add(lastVolta);
                                                lastVolta = 0;
                                                }
                                          else {
                                                printf("lastVolta == 0 on stop\n");
                                                }
                                          }
                                    else if (endingType == "discontinue") {
                                          if (lastVolta) {
                                                lastVolta->setTick2(tick);
                                                lastVolta->setSubtype(Volta::VOLTA_OPEN);
                                                score->add(lastVolta);
                                                lastVolta = 0;
                                                }
                                          else {
                                                printf("lastVolta == 0 on discontinue\n");
                                                }
                                          }
                                    else
                                          printf("ImportXml: warning: unsupported ending type <%s>\n",
                                                 endingType.toLatin1().data());
                                    }
                              }
                        }
                  }
            else if (e.tagName() == "sound")
                  domNotImplemented(e);
            else if (e.tagName() == "harmony")
                  xmlHarmony(e, tick, measure, staff);
            else
                  domError(e);
            }

      // if number of ticks in the measure does not match the nominal time signature
      // set a different actual time signature

      AL::TimeSigMap* sigmap = score->sigmap();
      int mtick        = measure->tick();

      if (measureLen != sigmap->ticksMeasure(mtick)) {

            AL::SigEvent se = sigmap->timesig(mtick);
            Fraction nominal = se.getNominal();
            // printf("measure %d actual len %d at %d -> nominal %d: %s\n",
            //        number+1, measureLen, mtick, sigmap->ticksMeasure(tick), qPrintable(nominal.print()));

            // determine actual duration as fraction
            Fraction actual;
            if ((measureLen % AL::division) == 0)
                  actual = Fraction(measureLen / AL::division, 4);
            else if ((measureLen % (AL::division/2)) == 0)
                  actual = Fraction(measureLen / (AL::division/2), 8);
            else if ((measureLen % (AL::division/4)) == 0)
                  actual = Fraction(measureLen / (AL::division/4), 16);
            else if ((measureLen % (AL::division/8)) == 0)
                  actual = Fraction(measureLen / (AL::division/8), 32);
            else {
                  // this shouldn't happen
                  printf("ImportXml: incorrect measure length calculated by determineMeasureLength()\n");
                  }

            // set time signature
            // printf("Add Sig %d  len %d:  %s\n", mtick, measureLen, qPrintable(actual.print()));
            score->sigmap()->add(mtick, actual, nominal);
            }

      // multi-measure rest handling:
      // the first measure in a multi-measure rest gets setBreakMultiMeasureRest(true)
      // and count down the remaining number of measures
      // the first measure after a multi-measure rest gets setBreakMultiMeasureRest(true)
      // for all other measures breakMultiMeasureRest is unchanged (stays default (false))

      if (startMultiMeasureRest) {
            measure->setBreakMultiMeasureRest(true);
            startMultiMeasureRest = false;
            }
      else {
            if (multiMeasureRestCount > 0) {
                  // measure is continuation of a multi-measure rest
                  --multiMeasureRestCount;
                  }
            else if (multiMeasureRestCount == 0) {
                  // measure is first measure after a multi-measure rest
                  measure->setBreakMultiMeasureRest(true);
                  --multiMeasureRestCount;
                  }
            }

      // printf("xmlMeasure %d end maxtick=%d\n", number, maxtick);
      return measure;
      }

//---------------------------------------------------------
//   setSLinePlacement -- helper for direction
//---------------------------------------------------------

static void setSLinePlacement(SLine* sli, float s, const QString pl, bool hasYoff, qreal yoff)
      {
      // printf("setSLinePlacement s=%g pl='%s' hasy=%d yoff=%g\n",
      //        s, qPrintable(pl), hasYoff, yoff
      //        );
      float offs = 0.0;
      if (hasYoff) offs = yoff;
      else {
            if (pl == "above")
                  offs = -3;
            else if (pl == "below")
                  offs = 8;
            else
                  printf("setSLinePlacement invalid placement '%s'\n", qPrintable(pl));
            }
      //LineSegment* ls = sli->lineSegments().front();
      //ls->setUserOff(QPointF(0, offs * s));
      sli->setUserOff(QPointF(0, offs * s));
      }

//---------------------------------------------------------
//   metronome
//---------------------------------------------------------

static void addSymbolToText(const SymCode& s, QTextCursor* cur)
      {
      QTextCharFormat oFormat = cur->charFormat();
      if (s.fontId >= 0) {
            QTextCharFormat oFormat = cur->charFormat();
            QTextCharFormat nFormat(oFormat);
            nFormat.setFontFamily(fontId2font(s.fontId).family());
            cur->setCharFormat(nFormat);
            cur->insertText(s.code);
            cur->setCharFormat(oFormat);
            }
      else
            cur->insertText(s.code);
      }

/**
 Read the MusicXML metronome element.
 */

/*
          <metronome parentheses="yes">
            <beat-unit>quarter</beat-unit>
            <beat-unit-dot/>
            <per-minute>50</per-minute>
            </metronome>
          <metronome parentheses="yes">
            <beat-unit>quarter</beat-unit>
            <beat-unit-dot/>
            <beat-unit>half</beat-unit>
            <beat-unit-dot/>
            </metronome>
*/

static void metronome(QDomElement e, Text* t)
      {
      bool textAdded = false;
      QTextDocument* d = t->doc();
      QTextCursor c(d);
      c.movePosition(QTextCursor::EndOfLine);
      QString parenth = e.attribute("parentheses");
      if (parenth == "yes") c.insertText("(");
      for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "beat-unit") {
                  if (textAdded) c.insertText(" = ");
                  QString txt = e.text();
                  if (txt == "breve") addSymbolToText(SymCode(0xe100, 1), &c);
                  else if (txt == "whole") addSymbolToText(SymCode(0xe101, 1), &c);
                  else if (txt == "half") addSymbolToText(SymCode(0xe104, 1), &c);
                  else if (txt == "quarter") addSymbolToText(SymCode(0xe105, 1), &c);
                  else if (txt == "eighth") addSymbolToText(SymCode(0xe106, 1), &c);
                  else if (txt == "16th") addSymbolToText(SymCode(0xe107, 1), &c);
                  else if (txt == "32nd") addSymbolToText(SymCode(0xe108, 1), &c);
                  else if (txt == "64th") addSymbolToText(SymCode(0xe109, 1), &c);
                  else c.insertText(e.text());
                  textAdded = true;
                  }
            else if (e.tagName() == "beat-unit-dot") {
                  addSymbolToText(SymCode(0xe10a, 1), &c);
                  textAdded = true;
                  }
            else if (e.tagName() == "per-minute") {
                  if (textAdded) c.insertText(" = ");
                  c.insertText(e.text());
                  textAdded = true;
                  }
            else
                  domError(e);
            } // for (e = e.firstChildElement(); ...
      if (parenth == "yes") c.insertText(")");
      }

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

/**
 Read the MusicXML direction element.
 */

// LVI FIXME: introduce offset concept to mscore.
// offset changes only the print position (not the tick), but unlike relative-x
// it is expressed in terms of divisions (MusicXML direction.dtd)
// even though the DTD does not mention it, practically speaking
// offset and relative-x are mutually exclusive

// Typical example:
// <direction placement="above">
//   <direction-type>
//      <words>Fine</words>
//      </direction-type>
//   <sound fine="yes"/>
//   </direction>

// Note: multiple direction-type (and multiple words) elements may be present,
// and at least one direction-type must be present but sound is optional and
// at most one can be present.

void MusicXml::direction(Measure* measure, int staff, QDomElement e)
      {
      // printf("MusicXml::direction\n");
      QString placement = e.attribute("placement");

      QString dirType;
      QString type;
      QString txt;
      QString lang;
      QString fontWeight = "";
      QString fontStyle = "";
      QString fontSize = "";
      int offset = 0;
      int rstaff = 0;
      QStringList dynamics;
      // int spread = 0; // not used
      qreal rx = 0.0;
      qreal ry = 0.0;
      qreal yoffset = 0.0; // actually this is default-y
      // qreal xoffset = 0.0; // not used
      bool hasYoffset = false;
      QString dynaVelocity = "";
      QString tempo = "";
      QString rehearsal = "";
      QString sndCapo = "";
      QString sndCoda = "";
      QString sndDacapo = "";
      QString sndDalsegno = "";
      QString sndSegno = "";
      QString sndFine = "";
      bool coda = false;
      bool segno = false;
      int ottavasize = 0;
      bool pedalLine = false;
      int number = 1;
      QString lineEnd;
      // qreal endLength = 0; // not used
      QString lineType;
      QDomElement metrEl;

      for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "direction-type") {
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        dirType = ee.tagName();
                        ry      = ee.attribute(QString("relative-y"), "0").toDouble() * -.1;
                        rx      = ee.attribute(QString("relative-x"), "0").toDouble() * .1;
                        yoffset = ee.attribute("default-y").toDouble(&hasYoffset) * -0.1;
                        // xoffset = ee.attribute("default-x", "0.0").toDouble() * 0.1;
                        if (dirType == "words") {
                              txt        = ee.text();
                              lang       = ee.attribute(QString("xml:lang"), "it");
                              fontWeight = ee.attribute(QString("font-weight"));
                              fontSize   = ee.attribute(QString("font-size"));
                              fontStyle  = ee.attribute(QString("font-style"));
                              }
                        else if (dirType == "rehearsal") {
                              rehearsal = ee.text();
                              }
                        else if (dirType == "pedal") {
                              type = ee.attribute(QString("type"));
                              pedalLine = ee.attribute(QString("line")) == "yes";
                              }
                        else if (dirType == "dynamics") {
                              QDomElement eee = ee.firstChildElement();
                              if (!eee.isNull()) {
                                    if (eee.tagName() == "other-dynamics")
                                          dynamics.push_back(eee.text());
                                    else
                                          dynamics.push_back(eee.tagName());
                                    }
                              }
                        else if (dirType == "wedge") {
                              type   = ee.attribute(QString("type"));
                              // spread = ee.attribute(QString("spread"), "0").toInt();
                              }
                        else if (dirType == "dashes") {
                              type      = ee.attribute(QString("type"));
                              number    = ee.attribute(QString("number"), "1").toInt();
                              }
                        else if (dirType == "bracket") {
                              type      = ee.attribute(QString("type"));
                              number    = ee.attribute(QString("number"), "1").toInt();
                              lineEnd   = ee.attribute(QString("line-end"), "none");
                              // endLength = ee.attribute(QString("end-length"), "0").toDouble() * 0.1;
                              lineType  = ee.attribute(QString("line-type"), "solid");
                              }
                        else if (dirType == "metronome")
                              metrEl = ee;
                        else if (dirType == "octave-shift") {
                              type       = ee.attribute(QString("type"));
                              ottavasize = ee.attribute(QString("size"), "0").toInt();
                              }
                        else if (dirType == "coda")
                              coda = true;
                        else if (dirType == "segno")
                              segno = true;
                        else
                              domError(ee);
                        }
                  }
            else if (e.tagName() == "sound") {
                  // attr: dynamics, tempo
                  // LVIFIX: TODO coda and segno should be numbered uniquely
                  sndCapo = e.attribute("capo");
                  sndCoda = e.attribute("coda");
                  sndDacapo = e.attribute("dacapo");
                  sndDalsegno = e.attribute("dalsegno");
                  sndFine = e.attribute("fine");
                  sndSegno = e.attribute("segno");
                  tempo = e.attribute("tempo");
                  dynaVelocity = e.attribute("dynamics");
                  }
            else if (e.tagName() == "offset")
                  offset = (e.text().toInt() * AL::division)/divisions;
            else if (e.tagName() == "staff") {
                  // DEBUG: <staff>0</staff>
                  rstaff = e.text().toInt() - 1;
                  if (rstaff < 0)         // ???
                        rstaff = 0;
                  }
            else
                  domError(e);
            } // for (e = e.firstChildElement(); ...

      /*
      printf(" tempo=%s txt=%s metrEl=%s coda=%d segno=%d sndCapo=%s sndCoda=%s"
             " sndDacapo=%s sndDalsegno=%s sndFine=%s sndSegno=%s\n",
             tempo.toLatin1().data(),
             txt.toLatin1().data(),
             qPrintable(metrEl.tagName()),
             coda,
             segno,
             sndCapo.toLatin1().data(),
             sndCoda.toLatin1().data(),
             sndDacapo.toLatin1().data(),
             sndDalsegno.toLatin1().data(),
             sndFine.toLatin1().data(),
             sndSegno.toLatin1().data()
            );
      */

      // Try to recognize the various repeats
      QString repeat = "";
      // Easy cases first
      if (coda) repeat = "coda";
      if (segno) repeat = "segno";
      // As sound may be missing, next do a wild-card match with known repeat texts
      QString lowerTxt = txt.toLower();
      QRegExp daCapo("d\\.? *c\\.?|da *capo");
      QRegExp daCapoAlFine("d\\.? *c\\.? *al *fine|da *capo *al *fine");
      QRegExp daCapoAlCoda("d\\.? *c\\.? *al *coda|da *capo *al *coda");
      QRegExp dalSegno("d\\.? *s\\.?|d[ae]l *segno");
      QRegExp dalSegnoAlFine("d\\.? *s\\.? *al *fine|d[ae]l *segno *al *fine");
      QRegExp dalSegnoAlCoda("d\\.? *s\\.? *al *coda|d[ae]l *segno *al *coda");
      QRegExp fine("fine");
      QRegExp toCoda("to *coda");
      if (daCapo.exactMatch(lowerTxt)) repeat = "daCapo";
      if (daCapoAlFine.exactMatch(lowerTxt)) repeat = "daCapoAlFine";
      if (daCapoAlCoda.exactMatch(lowerTxt)) repeat = "daCapoAlCoda";
      if (dalSegno.exactMatch(lowerTxt)) repeat = "dalSegno";
      if (dalSegnoAlFine.exactMatch(lowerTxt)) repeat = "dalSegnoAlFine";
      if (dalSegnoAlCoda.exactMatch(lowerTxt)) repeat = "dalSegnoAlCoda";
      if (fine.exactMatch(lowerTxt)) repeat = "fine";
      if (toCoda.exactMatch(lowerTxt)) repeat = "toCoda";
      // If that did not work, try to recognize a sound attribute
      if (repeat == "" && sndCoda != "") repeat = "coda";
      if (repeat == "" && sndDacapo != "") repeat = "daCapo";
      if (repeat == "" && sndDalsegno != "") repeat = "dalSegno";
      if (repeat == "" && sndFine != "") repeat = "fine";
      if (repeat == "" && sndSegno != "") repeat = "segno";
      // If a repeat was found, assume words is no longer needed
      if (repeat != "") txt = "";

      /*
      printf(" txt=%s repeat=%s\n",
             txt.toLatin1().data(),
             repeat.toLatin1().data()
            );
      */

      if (repeat != "") {
            Element* e = 0;
            if (repeat == "segno") {
                  e = new Marker(score);
                  ((Marker*)e)->setMarkerType(MARKER_SEGNO);
                  }
            else if (repeat == "coda") {
                  Marker* m = new Marker(score);
                  m->setMarkerType(MARKER_CODA);
                  e = m;
                  }
            else if (repeat == "fine") {
                  Marker* m = new Marker(score);
                  m->setMarkerType(MARKER_FINE);
                  e = m;
                  }
            else if (repeat == "toCoda") {
                  Marker* m = new Marker(score);
                  m->setMarkerType(MARKER_TOCODA);
                  e = m;
                  }
            else if (repeat == "daCapo") {
                  Jump* jp = new Jump(score);
                  jp->setJumpType(JUMP_DC);
                  e = jp;
                  }
            else if (repeat == "daCapoAlCoda") {
                  Jump* jp = new Jump(score);
                  jp->setJumpType(JUMP_DC_AL_CODA);
                  e = jp;
                  }
            else if (repeat == "daCapoAlFine") {
                  Jump* jp = new Jump(score);
                  jp->setJumpType(JUMP_DC_AL_FINE);
                  e = jp;
                  }
            else if (repeat == "dalSegno") {
                  Jump* jp = new Jump(score);
                  jp->setJumpType(JUMP_DS);
                  e = jp;
                  }
            else if (repeat == "dalSegnoAlCoda") {
                  Jump* jp = new Jump(score);
                  jp->setJumpType(JUMP_DS_AL_CODA);
                  e = jp;
                  }
            else if (repeat == "dalSegnoAlFine") {
                  Jump* jp = new Jump(score);
                  jp->setJumpType(JUMP_DS_AL_FINE);
                  e = jp;
                  }
            if (e) {
                  e->setTrack((staff + rstaff) * VOICES);
                  measure->add(e);
                  }
            }

      if ((dirType == "words" && repeat == "") || dirType == "metronome") {
            /*
            printf("words txt='%s' metrEl='%s' tempo='%s' pl='%s' hasyoffs=%d fsz='%s' fst='%s' fw='%s'\n",
                    txt.toUtf8().data(),
                    qPrintable(metrEl.tagName()),
                    tempo.toUtf8().data(),
                    placement.toUtf8().data(),
                    hasYoffset,
                    fontSize.toUtf8().data(),
                    fontStyle.toUtf8().data(),
                    fontWeight.toUtf8().data()
                  );
            */
            Text* t;
            if (tempo != "" && tempo.toDouble() > 0) {
                  t = new TempoText(score);
                  double tpo = tempo.toDouble()/60.0;
                  ((TempoText*) t)->setTempo(tpo);
                  AL::TempoMap* tl = score->tempomap();
                  if (tl) tl->addTempo(tick, tpo);
                  }
            else {
                  t = new Text(score);
                  t->setTextStyle(TEXT_STYLE_TECHNIK);
                  }
            t->setTick(tick);
            if (fontSize != "" || fontStyle != "" || fontWeight != "") {
                  QFont f = t->defaultFont();
                  if (fontSize != "") {
                        bool ok = true;
                        float size = fontSize.toFloat();
                        if (ok) f.setPointSizeF(size);
                        }
                  f.setItalic(fontStyle == "italic");
                  f.setBold(fontWeight == "bold");
                  t->setDefaultFont(f);
                  }
            t->setText(txt);
            if (metrEl.tagName() != "") metronome(metrEl, t);
            if (hasYoffset) t->setYoff(yoffset);
            else t->setAbove(placement == "above");
            t->setUserOff(QPointF(rx, ry));
            t->setMxmlOff(offset);
            if (tempo != "")
                  t->setTrack(0);  //Track 0 for tempo text (system text)
            else
                  t->setTrack((staff + rstaff) * VOICES);
            measure->add(t);
            }
      else if (dirType == "rehearsal") {
            Text* t = new Text(score);
            t->setSubtype(TEXT_REHEARSAL_MARK);
            t->setTextStyle(TEXT_STYLE_REHEARSAL_MARK);
            t->setTick(tick);
            t->setText(rehearsal);
            if (hasYoffset) t->setYoff(yoffset);
            else t->setAbove(placement == "above");
            t->setUserOff(QPointF(rx, ry));
            t->setMxmlOff(offset);
            t->setTrack((staff + rstaff) * VOICES);
            measure->add(t);
            }
      else if (dirType == "pedal") {
            if (pedalLine) {
                  if (type == "start") {
                        if (pedal) {
                              printf("overlapping pedal lines not supported\n");
                              delete pedal;
                              pedal = 0;
                              }
                        else {
                              pedal = new Pedal(score);
                              pedal->setTrack((staff + rstaff) * VOICES);
                              pedal->setTick(tick);
                              if (placement == "") placement = "below";
                              setSLinePlacement(pedal,
                                                score->spatium(), placement,
                                                hasYoffset, yoffset);
                              }
                        }
                  else if (type == "stop") {
                        if (!pedal) {
                              printf("pedal line stop without start\n");
                              }
                        else {
                              pedal->setTick2(tick);
                              score->add(pedal);
                              pedal = 0;
                              }
                        }
                  else
                        printf("unknown pedal %s\n", type.toLatin1().data());
                  }
            else {
                  Symbol* s = new Symbol(score);
                  s->setAlign(ALIGN_LEFT | ALIGN_BASELINE);
                  s->setOffsetType(OFFSET_SPATIUM);
                  s->setTick(tick);
                  if (type == "start")
                        s->setSym(pedalPedSym);
                  else if (type == "stop")
                        s->setSym(pedalasteriskSym);
                  else
                        printf("unknown pedal %s\n", type.toLatin1().data());
                  if (hasYoffset) s->setYoff(yoffset);
                  else s->setAbove(placement == "above");
                  s->setUserOff(QPointF(rx, ry));
                  s->setMxmlOff(offset);
                  s->setTrack((staff + rstaff) * VOICES);
                  measure->add(s);
                  }
            }
      else if (dirType == "dynamics") {
            // more than one dynamic ???
            // LVIFIX: check import/export of <other-dynamics>unknown_text</...>
            for (QStringList::Iterator it = dynamics.begin(); it != dynamics.end(); ++it ) {
                  Dynamic* dyn = new Dynamic(score);
                  dyn->setSubtype(*it);
                  if (hasYoffset) dyn->setYoff(yoffset);
                  else dyn->setAbove(placement == "above");
                  dyn->setUserOff(QPointF(rx, ry));
                  dyn->setMxmlOff(offset);
                  dyn->setTrack((staff + rstaff) * VOICES);
                  dyn->setTick(tick);
                  if (!dynaVelocity.isEmpty()) {
                        int dynaValue = round(dynaVelocity.toDouble() * 0.9);
                        if (dynaValue > 127)
                              dynaValue = 127;
                        else if (dynaValue < 0)
                              dynaValue = 0;
                        dyn->setVelocity( dynaValue );
                        }
                  measure->add(dyn);
                  }
            }
      else if (dirType == "wedge") {
            bool above = (placement == "above");
            if (type == "crescendo")
                  addWedge(0, tick, rx, ry, above, hasYoffset, yoffset, 0);
            else if (type == "stop")
                  genWedge(0, tick, measure, staff+rstaff);
            else if (type == "diminuendo")
                  addWedge(0, tick, rx, ry, above, hasYoffset, yoffset, 1);
            else
                  printf("unknown wedge type: %s\n", type.toLatin1().data());
            }
      else if (dirType == "bracket") {
            int n = number-1;
            TextLine* b = bracket[n];
            if (type == "start") {
                  if (b) {
                        printf("overlapping bracket with same number?\n");
                        delete b;
                        bracket[n] = 0;
                        }
                  else {
                        b = new TextLine(score);

                        // what does placement affect?
                        //yoffset += (placement == "above" ? 0.0 : 5.0);
                        // store for later to set in segment
                        // b->setUserOff(QPointF(rx + xoffset, ry + yoffset));
                        b->setMxmlOff(offset);
                        if (placement == "") placement = "above";  // set default
                        setSLinePlacement(b,
                                          score->spatium(), placement,
                                          hasYoffset, yoffset);

                        b->setBeginHook(lineEnd != "none");
                        if (lineEnd == "up")
                              b->setBeginHookHeight(-1 * b->beginHookHeight());

                        // hack: assume there was a words element before the bracket
                        if (!txt.isEmpty()) {
                              b->setBeginText(txt);
                              }

                        if (lineType == "solid")
                              b->setLineStyle(Qt::SolidLine);
                        else if (lineType == "dashed")
                              b->setLineStyle(Qt::DashLine);
                        else if (lineType == "dotted")
                              b->setLineStyle(Qt::DotLine);
                        else
                              printf("unsupported line-type: %s\n", lineType.toLatin1().data());

                        b->setTrack((staff + rstaff) * VOICES);
                        b->setTick(tick);
                        bracket[n] = b;
                        }
                  }
            else if (type == "stop") {
                  if (!b) {
                        printf("bracket stop without start, number %d\n", number);
                        }
                  else {
                        b->setTick2(tick);
                        // TODO: MuseScore doesn't support lines which start and end on different staves
                        /*
                        QPointF userOff = b->userOff();
                        b->add(b->createLineSegment());

                        b->setUserOff(QPointF()); // restore the offset
                        b->setMxmlOff2(offset);
                        LineSegment* ls1 = b->lineSegments().front();
                        LineSegment* ls2 = b->lineSegments().back();
                        // what does placement affect?
                        //yoffset += (placement == "above" ? 0.0 : 5.0);
                        ls1->setUserOff(userOff);
                        ls2->setUserOff2(QPointF(rx + xoffset, ry + yoffset));
                        */
                        b->setEndHook(lineEnd != "none");
                        if (lineEnd == "up")
                              b->setEndHookHeight(-1 * b->endHookHeight());
                        score->add(b);
                        bracket[n] = 0;
                        }
                  }
            }
      else if (dirType == "dashes") {
            int n = number-1;
            TextLine* b = dashes[n];
            if (type == "start") {
                  if (b) {
                        printf("overlapping dashes with same number?\n");
                        delete b;
                        dashes[n] = 0;
                        }
                  else {
                        b = new TextLine(score);

                        // what does placement affect?
                        //yoffset += (placement == "above" ? 0.0 : 5.0);
                        // store for later to set in segment
                        // b->setUserOff(QPointF(rx + xoffset, ry + yoffset));
                        b->setMxmlOff(offset);
                        if (placement == "") placement = "above";  // set default
                        setSLinePlacement(b,
                                          score->spatium(), placement,
                                          hasYoffset, yoffset);

                        // hack: assume there was a words element before the dashes
                        if (!txt.isEmpty()) {
                              b->setBeginText(txt);
                              }

                        b->setBeginHook(false);
                        b->setLineStyle(Qt::DashLine);
                        b->setTrack((staff + rstaff) * VOICES);
                        b->setTick(tick);
                        dashes[n] = b;
                        }
                  }
            else if (type == "stop") {
                  if (!b) {
                        printf("dashes stop without start, number %d\n", number);
                        }
                  else {
                        b->setTick2(tick);
                        // TODO: MuseScore doesn't support lines which start and end on different staves
                        /*
                        QPointF userOff = b->userOff();
                        b->add(b->createLineSegment());

                        b->setUserOff(QPointF()); // restore the offset
                        b->setMxmlOff2(offset);
                        LineSegment* ls1 = b->lineSegments().front();
                        LineSegment* ls2 = b->lineSegments().back();
                        // what does placement affect?
                        //yoffset += (placement == "above" ? 0.0 : 5.0);
                        ls1->setUserOff(userOff);
                        ls2->setUserOff2(QPointF(rx + xoffset, ry + yoffset));
                        */
                        b->setEndHook(false);
                        score->add(b);
                        dashes[n] = 0;
                        }
                  }
            }
      else if (dirType == "octave-shift") {
            if (type == "down") {
                  if (ottava) {
                        printf("overlapping octave-shift not supported\n");
                        delete ottava;
                        ottava = 0;
                        }
                  else {
                        ottava = new Ottava(score);
                        ottava->setTrack((staff + rstaff) * VOICES);
                        ottava->setTick(tick);
                        if (ottavasize == 8)
                              ottava->setSubtype(0);
                        else if (ottavasize == 15)
                              ottava->setSubtype(1);
                        else {
                              printf("unknown octave-shift size %d\n", ottavasize);
                              delete ottava;
                              ottava = 0;
                              }
                        if (placement == "") placement = "above";  // set default
                        if (ottava) setSLinePlacement(ottava,
                                                      score->spatium(), placement,
                                                      hasYoffset, yoffset);
                        }
                  }
            else if (type == "up") {
                  if (ottava) {
                        printf("overlapping octave-shift not supported\n");
                        delete ottava;
                        ottava = 0;
                        }
                  else {
                        ottava = new Ottava(score);
                        ottava->setTrack((staff + rstaff) * VOICES);
                        ottava->setTick(tick);
                        if (ottavasize == 8)
                              ottava->setSubtype(2);
                        else if (ottavasize == 15)
                              ottava->setSubtype(3);
                        else {
                              printf("unknown octave-shift size %d\n", ottavasize);
                              delete ottava;
                              ottava = 0;
                              }
                        if (placement == "") placement = "below";  // set default
                        if (ottava) setSLinePlacement(ottava,
                                                      score->spatium(), placement,
                                                      hasYoffset, yoffset);
                        }
                  }
            else if (type == "stop") {
                  if (!ottava) {
                        printf("octave-shift stop without start\n");
                        }
                  else {
                        ottava->setTick2(tick);
                        score->add(ottava);
                        ottava = 0;
                        }
                  }
            }
      }

//---------------------------------------------------------
//   xmlAttributes
//---------------------------------------------------------

/**
 Read the MusicXML attributes element.
 */

// Standard order of attributes as written by Dolet for Finale is divisions,
// key, time, staves and clef(s). For the first measure this means number of
// staves is unknown when the time attributes is read. As this translates
// into a time signature that must be inserted into every staff of the
// part, delay insertion of time signatures until after all attributes
// have been read.

void MusicXml::xmlAttributes(Measure* measure, int staff, QDomElement e)
      {
      QString beats = "";
      QString beatType = "";
      QString timeSymbol = "";

      int staves = 1; // default is one staff
      for (QDomElement e2 = e; !e2.isNull(); e2 = e2.nextSiblingElement()) {
            if (e2.tagName() == "staves") {
                  staves = e2.text().toInt();
                  Part* part = score->part(staff);
                  part->setStaves(staves);
                  // grow tuplets size, do not shrink to prevent losing info
                  if (staves * VOICES > tuplets.size())
                        tuplets.resize(staves * VOICES);
                  Staff* st = part->staff(0);
                  if (st && staves == 2) {
                        st->setBracket(0, BRACKET_AKKOLADE);
                        st->setBracketSpan(0, 2);
                        st->setBarLineSpan(2); //seems to be default in musicXML
                        }
                  }
            }

      for (; !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "divisions") {
                  bool ok;
                  divisions = stringToInt(e.text(), &ok);
                  if (!ok) {
                        printf("MusicXml-Import: bad divisions value: <%s>\n",
                               qPrintable(e.text()));
                        divisions = 4;
                        }
                  }
            else if (e.tagName() == "key") {
                  int number = e.attribute(QString("number"), "-1").toInt();
                  QString printObject(e.attribute("print-object", "yes"));
                  int staffIdx = staff;
                  if (number != -1)
                        staffIdx += number - 1;
                  KeySigEvent key;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "fifths")
                              key.setAccidentalType(ee.text().toInt());
                        else if (ee.tagName() == "mode")
                              domNotImplemented(ee);
                        else
                              domError(ee);
                        }
                  if (number == -1) {
                        //
                        //   apply key to all staves in the part
                        //   ws: number of staves is not always known at this
                        //       point, so we have to set key signature when
                        //       we see the "staves" tag
                        //
                        int staves = score->part(staff)->nstaves();
                        // apply to all staves in part
                        for (int i = 0; i < staves; ++i) {
                              KeySigEvent oldkey = score->staff(staffIdx+i)->keymap()->key(tick);
                              if (oldkey != key) {
                                    // new key differs from key in effect at this tick
                                    (*score->staff(staffIdx+i)->keymap())[tick] = key;
                                    KeySig* keysig = new KeySig(score);
                                    keysig->setTick(tick);
                                    keysig->setTrack((staffIdx + i) * VOICES);
                                    keysig->setSubtype(key);
                                    keysig->setVisible(printObject == "yes");
                                    Segment* s = measure->getSegment(keysig);
                                    s->add(keysig);
                                    }
                              }
                        }
                  else {
                        //
                        //    apply key to staff(staffIdx) only
                        //
                        KeySigEvent oldkey = score->staff(staffIdx)->keymap()->key(tick);
                        if (oldkey != key) {
                              // new key differs from key in effect at this tick
                              (*score->staff(staffIdx)->keymap())[tick] = key;
                              KeySig* keysig = new KeySig(score);
                              keysig->setTick(tick);
                              keysig->setTrack(staffIdx * VOICES);
                              keysig->setSubtype(key);
                              keysig->setVisible(printObject == "yes");
                              Segment* s = measure->getSegment(keysig);
                              s->add(keysig);
                              }
                        }
                  }
            else if (e.tagName() == "time") {
                  timeSymbol = e.attribute("symbol");
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "beats")
                              beats = ee.text();
                        else if (ee.tagName() == "beat-type") {
                              beatType = ee.text();
                              }
                        else if (ee.tagName() == "senza-misura")
                              ;
                        else
                              domError(ee);
                        }
                  }
            else if (e.tagName() == "clef")
                  xmlClef(e, staff, measure);
            else if (e.tagName() == "staves")
                  ;  // ignore, already handled
            else if (e.tagName() == "staff-details") {
                  int number  = e.attribute(QString("number"), "-1").toInt();
                  int staffIdx = staff;
                  if (number != -1)
                        staffIdx += number - 1;
                  int stafflines = 5;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "staff-lines")
                              stafflines = ee.text().toInt();
                        else
                              domNotImplemented(ee);
                        }

                  if (number == -1) {
                        int staves = score->part(staff)->nstaves();
                        for (int i = 0; i < staves; ++i) {
                              score->staff(staffIdx+i)->setLines(stafflines);
                              measure->mstaff(staffIdx+i)->lines->setLines(stafflines);
                              }
                        }
                  else {
                        score->staff(staffIdx)->setLines(stafflines);
                        measure->mstaff(staffIdx)->lines->setLines(stafflines);
                        }
                  }
            else if (e.tagName() == "instruments")
                  domNotImplemented(e);
            else if (e.tagName() == "transpose") {
                  Interval interval;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        int i = ee.text().toInt();
                        if (ee.tagName() == "diatonic")
                              interval.diatonic = i;
                        else if (ee.tagName() == "chromatic")
                              interval.chromatic = i;
                        else
                              domError(ee);
                        }
                  score->part(staff)->setTranspose(interval);
                  }
            else if (e.tagName() == "measure-style")
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "multiple-rest") {
                              int multipleRest = ee.text().toInt();
                              if (multipleRest > 1) {
                                    multiMeasureRestCount = multipleRest - 1;
                                    startMultiMeasureRest = true;
                                    }
                              else
                                    printf("ImportMusicXml: multiple-rest %d not supported\n",
                                           multipleRest);
                              }
                        else
                              domError(ee);
                        }
            else
                  domError(e);
            }
      if (beats != "" && beatType != "") {
            int st = determineTimeSig(beats, beatType, timeSymbol);
            if (st) {
                  // add timesig to all staves
                  score->sigmap()->add(tick, TimeSig::getSig(st));
                  Part* part = score->part(staff);
                  int staves = part->nstaves();
                  for (int i = 0; i < staves; ++i) {
                        TimeSig* timesig = new TimeSig(score);
                        timesig->setTick(tick);
                        timesig->setSubtype(st);
                        timesig->setTrack((staff + i) * VOICES);
                        Segment* s = measure->getSegment(timesig);
                        s->add(timesig);
                        }
                  }
            else
                  printf("unknown time signature, beats=<%s> beat-type=<%s> symbol=<%s>\n",
                         beats.toLatin1().data(), beatType.toLatin1().data(),
                         timeSymbol.toLatin1().data());
            }
      }

//---------------------------------------------------------
//   addLyrics -- add a single lyric to the score
//---------------------------------------------------------

static void addLyric(Measure* measure, Lyrics* l, int lyricNo)
      {
      l->setNo(lyricNo);
      Segment* segment = measure->getSegment(l);
      segment->add(l);
      }

//---------------------------------------------------------
//   addLyrics -- add a notes lyrics to the score
//---------------------------------------------------------

static void addLyrics(Measure* measure,
                      QMap<int, Lyrics*>& numbrdLyrics,
                      QMap<int, Lyrics*>& defyLyrics,
                      QList<Lyrics*>& unNumbrdLyrics)
      {
      // first the lyrics with valid number
      int lyricNo = -1;
      for (QMap<int, Lyrics*>::const_iterator i = numbrdLyrics.constBegin(); i != numbrdLyrics.constEnd(); ++i) {
            // printf("xmlLyric: numbrdLyrics[%d] %p\n", i.key(), i.value());
            lyricNo = i.key(); // use number obtained from MusicXML file
            Lyrics* l = i.value();
            addLyric(measure, l, lyricNo);
            }

      // then the lyrics without valid number but with valid default-y
      for (QMap<int, Lyrics*>::const_iterator i = defyLyrics.constBegin(); i != defyLyrics.constEnd(); ++i) {
            // printf("xmlLyric: defyLyrics[%d] %p\n", i.key(), i.value());
            lyricNo++; // use sequence number
            Lyrics* l = i.value();
            if (lyricNo > MAX_LYRICS) {
                  printf("too much lyrics (>%d)\n", MAX_LYRICS);
                  delete l;
                  }
            else {
                  addLyric(measure, l, lyricNo);
                  }
            }

      // finally the remaining lyrics, which are simply added in order they appear in the MusicXML file
      for (QList<Lyrics*>::const_iterator i = unNumbrdLyrics.constBegin(); i != unNumbrdLyrics.constEnd(); ++i) {
            // printf("xmlLyric: unNumbrdLyrics %p\n", *i);
            lyricNo++; // use sequence number
            Lyrics* l = *i;
            if (lyricNo > MAX_LYRICS) {
                  printf("too much lyrics (>%d)\n", MAX_LYRICS);
                  delete l;
                  }
            else {
                  addLyric(measure, l, lyricNo);
                  }
            }
      }

//---------------------------------------------------------
//   xmlLyric -- parse a MusicXML lyric element
//---------------------------------------------------------

void MusicXml::xmlLyric(int trk, QDomElement e,
                        QMap<int, Lyrics*>& numbrdLyrics,
                        QMap<int, Lyrics*>& defyLyrics,
                        QList<Lyrics*>& unNumbrdLyrics)
      {
      Lyrics* l = new Lyrics(score);
      l->setTick(tick);
      l->setTrack(trk);
      // printf("xmlLyric %p track %d\n", l, trk);

      bool ok = true;
      int lyricNo = e.attribute(QString("number")).toInt(&ok) - 1;
      printf("xmlLyric ok %d no %d\n", ok, lyricNo);
      if (ok) {
            if (lyricNo < 0) {
                  printf("invalid lyrics number (<0)\n");
                  delete l;
                  }
            else if (lyricNo > MAX_LYRICS) {
                  printf("too much lyrics (>%d)\n", MAX_LYRICS);
                  delete l;
                  }
            else {
                  numbrdLyrics[lyricNo] = l;
                  }
            }
      else {
            int defaultY = e.attribute(QString("default-y")).toInt(&ok);
            // printf("xmlLyric ok %d default-y %d\n", ok, defaultY);
            if (ok)
                  // invert default-y as it decreases with increasing lyric number
                  defyLyrics[-defaultY] = l;
            else
                  unNumbrdLyrics.append(l);
            }

      for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
            if (e.tagName() == "syllabic") {
                  if (e.text() == "single")
                        l->setSyllabic(Lyrics::SINGLE);
                  else if (e.text() == "begin")
                        l->setSyllabic(Lyrics::BEGIN);
                  else if (e.text() == "end")
                        l->setSyllabic(Lyrics::END);
                  else if (e.text() == "middle")
                        l->setSyllabic(Lyrics::MIDDLE);
                  else
                        printf("unknown syllabic %s\n", qPrintable(e.text()));
                  }
            else if (e.tagName() == "text")
                  l->setText(l->getText()+e.text());
            else if (e.tagName() == "elision") {
                  if (e.text().isEmpty())
                        l->setText(l->getText()+" ");
                  else
                        l->setText(l->getText()+e.text());
                  }
            else if (e.tagName() == "extend")
                  ;
            else if (e.tagName() == "end-line")
                  ;
            else if (e.tagName() == "end-paragraph")
                  ;
            else
                  domError(e);
            }
      }

//---------------------------------------------------------
//   hasElem
//---------------------------------------------------------

/**
 Determine if \a e has a child \a tagname.
 */

static bool hasElem(const QDomElement e, const QString& tagname)
      {
      return !e.elementsByTagName(tagname).isEmpty();
      }

//---------------------------------------------------------
//   nrOfGraceSegsReq
//---------------------------------------------------------

/**
 Determine the number of grace segments required for the grace note represented by \a n.
 */

static int nrOfGraceSegsReq(QDomNode n)
      {
      if (!n.isElement() || n.nodeName() != "note") return 0;
      QDomElement e = n.toElement();
      if (!hasElem(e, "grace")) return 0;
      int nsegs = 0;
      // when counting starts in a grace chord but not at the first note,
      // compensate for the missed first note
      if (hasElem(e, "chord")) nsegs = 1;
      // count the number of grace chords
      // i.e the number of notes with grace but without chord child elements
      for (; !e.isNull(); e = e.nextSiblingElement()) {
            QString tag(e.tagName());
            if (tag != "note") {
                  ;
                  // printf("nrOfGraceSegsReq: found non-note tag '%s'\n",
                  //        qPrintable(tag));
                  }
            if (!hasElem(e, "grace"))
                  // non-grace note found, done
                  return nsegs;
            if (!hasElem(e, "chord"))
                  // first note of grace chord found
                  ++nsegs;
            }
      return 0;
      }

//---------------------------------------------------------
//   tupletAssert -- check assertions for tuplet handling
//---------------------------------------------------------

static void tupletAssert()
      {
      if (!(Duration::V_BREVE      == Duration::V_LONG + 1
            && Duration::V_WHOLE   == Duration::V_BREVE + 1
            && Duration::V_HALF    == Duration::V_WHOLE + 1
            && Duration::V_QUARTER == Duration::V_HALF + 1
            && Duration::V_EIGHT   == Duration::V_QUARTER + 1
            && Duration::V_16TH    == Duration::V_EIGHT + 1
            && Duration::V_32ND    == Duration::V_16TH + 1
            && Duration::V_64TH    == Duration::V_32ND + 1
            && Duration::V_128TH   == Duration::V_64TH + 1
            && Duration::V_256TH   == Duration::V_128TH + 1
            )) {
            printf("tupletAssert() failed\n");
            abort();
            }
      }

//---------------------------------------------------------
//   smallestTypeAndCount
//---------------------------------------------------------


/**
 Determine the smallest note type and the number of those
 present in a ChordRest.
 For a note without dots the type equals the note type
 and count is one.
 For a single dotted note the type equals half the note type
 and count is three.
 A double dotted note is similar.
 Note: code assumes when duration().type() is incremented,
 the note length is divided by two, checked by tupletAssert().
 */

static void smallestTypeAndCount(ChordRest const* const cr, int& type, int& count)
      {
      type = cr->duration().type();
      count = 1;
      switch (cr->duration().dots()) {
            case 0:
                  // nothing to do
                  break;
            case 1:
                  type += 1; // next-smaller type
                  count = 3;
                  break;
            case 2:
                  type += 2; // next-next-smaller type
                  count = 7;
                  break;
            default:
                  printf("smallestTypeAndCount() does not support more than 2 dots\n");
            }
      }

//---------------------------------------------------------
//   matchTypeAndCount
//---------------------------------------------------------

/**
 Given two note types and counts, if the types are not equal,
 make them equal by successively doubling the count of the
 largest type.
 */

static void matchTypeAndCount(int& type1, int& count1, int& type2, int& count2)
      {
      while (type1 < type2) {
            type1++;
            count1 *= 2;
            }
      while (type2 < type1) {
            type2++;
            count2 *= 2;
            }
      }

//---------------------------------------------------------
//   isTupletFilled
//---------------------------------------------------------

/**
 Determine if the tuplet contains the required number of notes,
 either (1) of the specified normal type
 or (2) the amount of the smallest notes in the tuplet equals
 actual notes.

 Example (1): a 3:2 tuplet with a 1/4 and a 1/8 note is filled
              if normal type is 1/8, it is not filled if normal
              type is 1/4.

 Example (2): a 3:2 tuplet with a 1/4 and a 1/8 note is filled.

 Use note types instead of duration to prevent errors due to rounding.
 */

bool isTupletFilled(Tuplet* t, Duration normalType)
      {
      if (!t) return false;

      int tupletType  = 0; // smallest note type in the tuplet
      int tupletCount = 0; // number of smallest notes in the tuplet
      int elemCount   = 0; // number of tuplet elements handled

      // first determine type and number of smallest notes in the tuplet
      foreach (DurationElement* de, t->elements()) {
            if (de->type() == CHORD || de->type() == REST) {
                  ChordRest* cr = static_cast<ChordRest*>(de);
                  if (elemCount == 0)
                        // first note: init variables
                        smallestTypeAndCount(cr, tupletType, tupletCount);
                  else {
                        int noteType = 0;
                        int noteCount = 0;
                        smallestTypeAndCount(cr, noteType, noteCount);
                        // match the types
                        matchTypeAndCount(tupletType, tupletCount, noteType, noteCount);
                        tupletCount += noteCount;
                        }
                  }
            elemCount++;
            }

      // then compare ...
      if (normalType.isValid()) {
            int matchedNormalType  = normalType.type();
            int matchedNormalCount = t->ratio().numerator();
            // match the types
            matchTypeAndCount(tupletType, tupletCount, matchedNormalType, matchedNormalCount);
            // ... result scenario (1)
            return tupletCount >= matchedNormalCount;
            }
      else
            // ... result scenario (2)
            return tupletCount >= t->ratio().numerator();
      }

//---------------------------------------------------------
//   xmlTuplet
//---------------------------------------------------------

/**
 Parse and handle tuplet(s)
 Tuplets with <actual-notes> and <normal-notes> but without <tuplet>
 are handled correctly.
 TODO Nested tuplets are not (yet) supported.

 Note that cr must be initialized: fields measure, score, tick
 and track are used.
 */

void xmlTuplet(Tuplet*& tuplet, ChordRest* cr, int ticks, QDomElement e)
      {
      int actualNotes = 1;
      int normalNotes = 1;
      Duration normalType;
      bool rest = false;
      QString tupletType;
      QString tupletPlacement;
      QString tupletBracket;
      QString tupletShowNumber;

      // parse the elements required for tuplet handling
      for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
            QString tag(e.tagName());
            QString s(e.text());
            if (tag == "notations") {
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "tuplet") {
                              tupletType       = ee.attribute("type");
                              tupletPlacement  = ee.attribute("placement");
                              tupletBracket    = ee.attribute("bracket");
                              tupletShowNumber = ee.attribute("show-number");
                              }
                        }
                  }
            else if (tag == "rest") {
                  rest = true;
                  }
            else if (tag == "time-modification") {  // tuplets
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "actual-notes")
                              actualNotes = ee.text().toInt();
                        else if (ee.tagName() == "normal-notes")
                              normalNotes = ee.text().toInt();
                        else if (ee.tagName() == "normal-type") {
                              // "measure" is not a valid normal-type,
                              // but would be accepted by setType()
                              if (ee.text() != "measure")
                                    normalType.setType(ee.text());
                              }
                        else
                              domError(ee);
                        }
                  }
            }

      // 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 (rest && actualNotes == 1 && normalNotes == 1 && cr->tickLen() != ticks) {
            printf("xmlTuplet --> found tuplets w/o <tuplet> or <time-modification> !\n");
            if (2 * cr->tickLen() == 3 * ticks) {
                  actualNotes = 3;
                  normalNotes = 2;
                  }
            }

      // check for obvious errors
      if (tupletType == "start" && tuplet) {
            printf("tuplet already started\n");
            // TODO: how to recover ?
            }
      if (tupletType == "stop" && !tuplet) {
            printf("tuplet stop but no tuplet started\n");
            // TODO: how to recover ?
            }
      if (tupletType != "" && tupletType != "start" && tupletType != "stop") {
            printf("unknown tuplet type %s\n", qPrintable(tupletType));
            }

      // Tuplet are either started by the tuplet start
      // or when the time modification is first found.
      if (!tuplet) {
            if (tupletType == "start"
                || (!tuplet && (actualNotes != 1 || normalNotes != 1))) {
                  tuplet = new Tuplet(cr->score());
                  tuplet->setTrack(cr->track());
                  tuplet->setRatio(Fraction(actualNotes, normalNotes));
                  tuplet->setTick(cr->tick());
                  // set bracket, leave at default if unspecified
                  if (tupletBracket == "yes")
                        tuplet->setBracketType(Tuplet::SHOW_BRACKET);
                  else if (tupletBracket == "no")
                        tuplet->setBracketType(Tuplet::SHOW_NO_BRACKET);
                  // set number, default is "actual" (=SHOW_NUMBER)
                  if (tupletShowNumber == "both")
                        tuplet->setNumberType(Tuplet::SHOW_RELATION);
                  else if (tupletShowNumber == "none")
                        tuplet->setNumberType(Tuplet::NO_TEXT);
                  else
                        tuplet->setNumberType(Tuplet::SHOW_NUMBER);
                  // TODO type, placement
                  cr->measure()->add(tuplet);
                  }
            }

      // Add chord to the current tuplet.
      // Must also check for actual/normal notes to prevent
      // adding one chord too much if tuplet stop is missing.
      if (tuplet && !(actualNotes == 1 && normalNotes == 1)) {
            cr->setTuplet(tuplet);
            tuplet->add(cr);
            }

      // Tuplets are stopped by the tuplet stop
      // or when the tuplet is filled completely
      // (either with knowledge of the normal type
      // or as a last resort calculated based on
      // actual and normal notes plus total duration)
      // or when the time-modification is not found.
      if (tuplet) {
            if (tupletType == "stop"
                || isTupletFilled(tuplet, normalType)
                || (actualNotes == 1 && normalNotes == 1)) {
                  int totalDuration = 0;
                  foreach (DurationElement* de, tuplet->elements()) {
                        if (de->type() == CHORD || de->type() == REST) {
                              totalDuration+=de->tickLen();
                              }
                        }
                  if (totalDuration && normalNotes) {
                        Duration d;
                        d.setVal(totalDuration);
                        tuplet->setFraction(d.fraction());
                        Duration d2;
                        d2.setVal(totalDuration/normalNotes);
                        tuplet->setBaseLen(d2.fraction());
                        tuplet = 0;
                        }
                  else {
                        printf("MusicXML::import: tuplet stop but bad duration\n");
                        }
                  }
            }
      }

//---------------------------------------------------------
//   addArticulationToChord
//---------------------------------------------------------

/**
 Add Articulation to Chord.
 */

static void addArticulationToChord(ChordRest* cr, int articSym)
      {
      Articulation* na = new Articulation(cr->score());
      na->setSubtype(articSym);
      cr->add(na);
      }

//---------------------------------------------------------
//   readArticulations
//---------------------------------------------------------

/**
 Read a "simple" MuseScore articulation.
 These are the articulations that can be
 - represented by an enum ArticulationIdx
 - added to a ChordRest
 Return true (articulation recognized and handled)
 or false (articulation not recognized).
 Note simple implementation: MusicXML syntax is not strictly
 checked, the articulations parent element does not matter.
 */

static bool readArticulations(ChordRest* cr, QString mxmlName)
      {
      QMap<QString, int> map; // map MusicXML articulation name to MuseScore symbol
      map["accent"]           = SforzatoaccentSym;
      map["staccatissimo"]    = UstaccatissimoSym;
      map["staccato"]         = StaccatoSym;
      map["tenuto"]           = TenutoSym;
      map["turn"]             = TurnSym;
      map["mordent"]          = MordentSym;
      map["inverted-mordent"] = PrallSym;
      map["inverted-turn"]    = ReverseturnSym;
      map["stopped"]          = PlusstopSym;
      map["up-bow"]           = UpbowSym;
      map["down-bow"]         = DownbowSym;
      map["detached-legato"]  = DportatoSym;
      map["spiccato"]         = UstaccatissimoSym;
      map["snap-pizzicato"]   = SnappizzicatoSym;

      if (map.contains(mxmlName)) {
            addArticulationToChord(cr, map.value(mxmlName));
            return true;
            }
      else
            return false;
      }

//---------------------------------------------------------
//   convertAccidental
//---------------------------------------------------------

/**
 Convert a MusicXML accidental name to a MuseScore enum AccidentalType.
 */

static int convertAccidental(QString mxmlName)
      {
      QMap<QString, int> map; // map MusicXML accidental name to MuseScore enum AccidentalType
      map["natural"] = ACC_NATURAL;
      map["flat"] = ACC_FLAT;
      map["sharp"] = ACC_SHARP;
      map["double-sharp"] = ACC_SHARP2;
      map["sharp-sharp"] = ACC_SHARP2;
      map["flat-flat"] = ACC_FLAT2;
      map["double-flat"] = ACC_FLAT2;

      map["natural-flat"] = ACC_NONE;

      map["quarter-flat"] = ACC_MIRRORED_FLAT;
      map["quarter-sharp"] = ACC_SHARP_SLASH;
      map["three-quarters-flat"] = ACC_MIRRORED_FLAT2;
      map["three-quarters-sharp"] = ACC_SHARP_SLASH4;

      map["sharp-down"] = ACC_SHARP_ARROW_DOWN;
      map["sharp-up"] = ACC_SHARP_ARROW_UP;
      map["natural-down"] = ACC_NATURAL_ARROW_DOWN;
      map["natural-up"] = ACC_NATURAL_ARROW_UP;
      map["flat-down"] = ACC_FLAT_ARROW_DOWN;
      map["flat-up"] = ACC_FLAT_ARROW_UP;

      map["slash-quarter-sharp"] = ACC_MIRRIRED_FLAT_SLASH;
      map["slash-sharp"] = ACC_SHARP_SLASH;
      map["slash-flat"] = ACC_FLAT_SLASH;
      map["double-slash-flat"] = ACC_FLAT_SLASH2;

      map["sori"] = ACC_SORI;
      map["koron"] = ACC_KORON;

      map["natural-sharp"] = ACC_NONE;

      if (map.contains(mxmlName))
            return map.value(mxmlName);
      else
            printf("unknown accidental %s\n", qPrintable(mxmlName));
      // default: return ACC_NONE
      return ACC_NONE;
      }

//---------------------------------------------------------
//   convertNotehead
//---------------------------------------------------------

/**
 Convert a MusicXML notehead name to a MuseScore headgroup.
 */

static int convertNotehead(QString mxmlName)
      {
      QMap<QString, int> map; // map MusicXML notehead name to a MuseScore headgroup
      map["slash"] = 5;
      map["triangle"] = 3;
      map["diamond"] = 2;
      map["x"] = 1;
      map["circle-x"] = 6;
      map["do"] = 7;
      map["re"] = 8;
      map["mi"] = 4;
      map["fa"] = 9;
      map["la"] = 10;
      map["ti"] = 11;
      map["normal"] = 0;

      if (map.contains(mxmlName))
            return map.value(mxmlName);
      else
            printf("unknown notehead %s\n", qPrintable(mxmlName));
      // default: return 0
      return 0;

      }

//---------------------------------------------------------
//   addTextToNote
//---------------------------------------------------------

/**
 Add Text to Note.
 */

static void addTextToNote(QString txt, int subtype, int style, Score* score, Note* note)
      {
      if (!txt.isEmpty() && note) {
            Text* t = new Text(score);
            t->setSubtype(subtype);
            t->setTextStyle(style);
            t->setText(txt);
            note->add(t);
            }
      }

//---------------------------------------------------------
//   addFermata
//---------------------------------------------------------

/**
 Add a MusicXML fermata.
 Note: MusicXML common.mod: "The fermata type is upright if not specified."
 */

static void addFermata(ChordRest* cr, const QString type, const int articSymUp, const int articSymInv)
      {
      if (type == "upright" || type == "")
            addArticulationToChord(cr, articSymUp);
      else if (type == "inverted")
            addArticulationToChord(cr, articSymInv);
      else
            printf("unknown fermata type '%s'\n", qPrintable(type));
      }

//---------------------------------------------------------
//   xmlFermata
//---------------------------------------------------------

/**
 Read a MusicXML fermata.
 Note: MusicXML common.mod: "An empty fermata element represents a normal fermata."
 */

static void xmlFermata(ChordRest* cr, QDomElement e)
      {
      QString fermata     = e.text();
      QString fermataType = e.attribute(QString("type"));

      if (fermata == "normal" || fermata == "")
            addFermata(cr, fermataType, UfermataSym, DfermataSym);
      else if (fermata == "angled")
            addFermata(cr, fermataType, UshortfermataSym, DshortfermataSym);
      else if (fermata == "square")
            addFermata(cr, fermataType, UlongfermataSym, DlongfermataSym);
      else
            printf("unknown fermata '%s'\n", qPrintable(fermata));
      }

//---------------------------------------------------------
//   xmlNotations
//---------------------------------------------------------

/**
 Read MusicXML notations.
 */

void MusicXml::xmlNotations(Note* note, ChordRest* cr, int trk, int ticks, QDomElement e)
      {
      Measure* measure = cr->measure();
      int track = cr->track();

      QString wavyLineType;
      QString arpeggioType;
      QString glissandoType;
      int breath = -1;
      int tremolo = 0;
      QString tremoloType;
      QString placement;
      QStringList dynamics;
      qreal rx = 0.0;
      qreal ry = 0.0;
      qreal yoffset = 0.0; // actually this is default-y
      // qreal xoffset = 0.0; // not used
      bool hasYoffset = false;
      QSet<QString> slurIds;             // combination start/stop and number must be unique within a note
      for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
            if (ee.tagName() == "slur") {
                  int slurNo   = ee.attribute(QString("number"), "1").toInt() - 1;
                  QString slurType = ee.attribute(QString("type"));

                  // PriMus Music-Notation by Columbussoft (build 10093) generates overlapping
                  // slurs that do not have a number attribute to distinguish them.
                  // The duplicates must be ignored, to prevent memory allocation issues,
                  // which caused a MuseScore crash

                  QString slurId = QString("slur %1").arg(slurType) + QString(" %1").arg(slurNo);
                  bool unique = !slurIds.contains(slurId);

                  if (unique) {
                        slurIds.insert(slurId);
                        if (slurType == "start") {
                              bool endSlur = false;
                              if (slur[slurNo] == 0) {
                                    slur[slurNo] = new Slur(score);
                                    cr->addSlurFor(slur[slurNo]);
                                    slur[slurNo]->setStartElement(cr);
                                    }
                              else
                                    endSlur = true;
                              QString pl = ee.attribute(QString("placement"));
                              if (pl == "above")
                                    slur[slurNo]->setSlurDirection(UP);
                              else if (pl == "below")
                                    slur[slurNo]->setSlurDirection(DOWN);
                              slur[slurNo]->setTrack(-1);
                              slur[slurNo]->setTick(-1);
                              score->add(slur[slurNo]);
                              if (endSlur) {
                                    cr->addSlurFor(slur[slurNo]);
                                    slur[slurNo]->setStartElement(cr);
                                    slur[slurNo] = 0;
                                    }
                              }
                        else if (slurType == "stop") {
                              if (slur[slurNo] == 0) {
                                    slur[slurNo] = new Slur(score);
                                    cr->addSlurBack(slur[slurNo]);
                                    slur[slurNo]->setEndElement(cr);
                                    }
                              else {
                                    cr->addSlurBack(slur[slurNo]);
                                    slur[slurNo]->setEndElement(cr);
                                    slur[slurNo] = 0;
                                    }
                              }
                        else
                              printf("unknown slur type %s\n", qPrintable(slurType));
                        }
                  else
                        printf("ignoring duplicate '%s'\n", qPrintable(slurId));
                  }

            else if (ee.tagName() == "tied") {
                  QString tiedType = ee.attribute(QString("type"));
                  if (tiedType == "start") {
                        if (tie) {
                              printf("Tie already active\n");
                              }
                        else {
                              if (!note)
                                    return;
                              tie = new Tie(score);
                              note->setTieFor(tie);
                              tie->setStartNote(note);
                              tie->setTrack(track);
                              tie = 0;
                              }
                        QString tiedOrientation = e.attribute("orientation", "auto");
                        if (tiedOrientation == "over")
                              tie->setSlurDirection(UP);
                        else if (tiedOrientation == "under")
                              tie->setSlurDirection(DOWN);
                        else if (tiedOrientation == "auto")
                              ;
                        else
                              printf("unknown tied orientation: %s\n", tiedOrientation.toLatin1().data());
                        }
                  else if (tiedType == "stop")
                        ;
                  else
                        printf("unknown tied type %s\n", tiedType.toLatin1().data());
                  }
            else if (ee.tagName() == "tuplet") {
                  // needed for tuplets, handled in xmlTuplet
                  }
            else if (ee.tagName() == "dynamics") {
                  placement = ee.attribute("placement");
                  ry        = ee.attribute(QString("relative-y"), "0").toDouble() * -.1;
                  rx        = ee.attribute(QString("relative-x"), "0").toDouble() * .1;
                  yoffset   = ee.attribute("default-y").toDouble(&hasYoffset) * -0.1;
                  // xoffset   = ee.attribute("default-x", "0.0").toDouble() * 0.1;
                  QDomElement eee = ee.firstChildElement();
                  if (!eee.isNull()) {
                        if (eee.tagName() == "other-dynamics")
                              dynamics.push_back(eee.text());
                        else
                              dynamics.push_back(eee.tagName());
                        }
                  }
            else if (ee.tagName() == "articulations") {
                  for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                        if (readArticulations(cr, eee.tagName()))
                              continue;
                        else if (eee.tagName() == "breath-mark")
                              breath = 0;
                        else if (eee.tagName() == "caesura")
                              breath = 3;
                        else if (eee.tagName() == "strong-accent") {
                              QString strongAccentType = eee.attribute(QString("type"));
                              if (strongAccentType == "up" || strongAccentType == "")
                                    addArticulationToChord(cr, UmarcatoSym);
                              else if (strongAccentType == "down")
                                    addArticulationToChord(cr, DmarcatoSym);
                              else
                                    printf("unknown mercato type '%s'\n", strongAccentType.toLatin1().data());
                              }
                        else
                              domError(eee);
                        }
                  }
            else if (ee.tagName() == "fermata")
                  xmlFermata(cr, ee);
            else if (ee.tagName() == "ornaments") {
                  bool trillMark = false;
                  // <trill-mark placement="above"/>
                  for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                        if (readArticulations(cr, eee.tagName()))
                              continue;
                        else if (eee.tagName() == "trill-mark")
                              trillMark = true;
                        else if (eee.tagName() == "wavy-line")
                              wavyLineType = eee.attribute(QString("type"));
                        else if (eee.tagName() == "tremolo") {
                              tremolo = eee.text().toInt();
                              tremoloType = eee.attribute(QString("type"));
                              }
                        else if (eee.tagName() == "accidental-mark")
                              domNotImplemented(eee);
                        else if (eee.tagName() == "delayed-turn")
                              // TODO: actually this should be offset a bit to the right
                              addArticulationToChord(cr, TurnSym);
                        else
                              domError(eee);
                        }
                  // note that mscore wavy line already implicitly includes a trillsym
                  // so don't add an additional one
                  if (trillMark && wavyLineType != "start")
                        addArticulationToChord(cr, TrillSym);
                  }
            else if (ee.tagName() == "technical") {
                  for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
                        if (readArticulations(cr, eee.tagName()))
                              continue;
                        else if (eee.tagName() == "fingering")
                              addTextToNote(eee.text(), TEXT_FINGERING, TEXT_STYLE_FINGERING, score, note);
                        else if (eee.tagName() == "fret")
                              domNotImplemented(eee);
                        else if (eee.tagName() == "pluck")
                              addTextToNote(eee.text(), TEXT_FINGERING, TEXT_STYLE_FINGERING, score, note);
                        else if (eee.tagName() == "string")
                              addTextToNote(eee.text(), TEXT_STRING_NUMBER, TEXT_STYLE_STRING_NUMBER, score, note);
                        else if (eee.tagName() == "pull-off")
                              domNotImplemented(eee);
                        else
                              domError(eee);
                        }
                  }
            else if (ee.tagName() == "arpeggiate") {
                  arpeggioType = ee.attribute(QString("direction"));
                  if (arpeggioType == "") arpeggioType = "none";
                  }
            else if (ee.tagName() == "non-arpeggiate")
                  arpeggioType = "non-arpeggiate";
            // glissando and slide are added to the "stop" chord only
            else if (ee.tagName() == "glissando") {
                  if (ee.attribute("type") == "stop") glissandoType = "glissando";
                  }
            else if (ee.tagName() == "slide") {
                  if (ee.attribute("type") == "stop") glissandoType = "slide";
                  }
            else
                  domError(ee);
            }

      if (!arpeggioType.isEmpty()) {
            Arpeggio* a = new Arpeggio(score);
            if (arpeggioType == "none")
                  a->setSubtype(0);
            else if (arpeggioType == "up")
                  a->setSubtype(1);
            else if (arpeggioType == "down")
                  a->setSubtype(2);
            else if (arpeggioType == "non-arpeggiate")
                  a->setSubtype(3);
            else {
                  printf("unknown arpeggio type %s\n", arpeggioType.toLatin1().data());
                  delete a;
                  a = 0;
                  }
            if ((static_cast<Chord*>(cr))->arpeggio()) {
                  // there can be only one
                  delete a;
                  a = 0;
                  }
            else
                  cr->add(a);
            }

      if (!glissandoType.isEmpty()) {
            Glissando* g = new Glissando(score);
            if (glissandoType == "slide")
                  g->setSubtype(0);
            else if (glissandoType == "glissando")
                  g->setSubtype(1);
            else {
                  printf("unknown glissando type %s\n", glissandoType.toLatin1().data());
                  delete g;
                  g = 0;
                  }
            if ((static_cast<Chord*>(cr))->glissando()) {
                  // there can be only one
                  delete g;
                  g = 0;
                  }
            else
                  cr->add(g);
            }

      if (!wavyLineType.isEmpty()) {
            if (wavyLineType == "start") {
                  if (trill) {
                        printf("overlapping wavy-line not supported\n");
                        delete trill;
                        trill = 0;
                        }
                  else {
                        trill = new Trill(score);
                        trill->setTrack(trk); // TODO: replace trk by value calculated from track ?
                        trill->setTick(tick);
                        }
                  }
            else if (wavyLineType == "stop") {
                  if (!trill) {
                        printf("wavy-line stop without start\n");
                        }
                  else {
                        trill->setTick2(tick+ticks); // TODO: replace trk by value calculated from chordrest tick + length ?
                        score->add(trill);
                        trill = 0;
                        }
                  }
            else
                  printf("unknown wavy-line type '%s'\n", wavyLineType.toLatin1().data());
            }

      if (breath >= 0) {
            Breath* b = new Breath(score);
            b->setTick(tick);
            b->setTrack(track);
            b->setSubtype(breath);
            Segment* seg = measure->getSegment(SegBreath, tick);
            seg->add(b);
            }

      if (tremolo) {
            // printf("tremolo=%d tremoloType='%s'\n", tremolo, qPrintable(tremoloType));
            if (tremolo == 1 || tremolo == 2 || tremolo == 3) {
                  if (tremoloType == "" || tremoloType == "single") {
                        Tremolo* t = new Tremolo(score);
                        if (tremolo == 1) t->setSubtype(TREMOLO_1);
                        if (tremolo == 2) t->setSubtype(TREMOLO_2);
                        if (tremolo == 3) t->setSubtype(TREMOLO_3);
                        cr->add(t);
                        }
                  else if (tremoloType == "start") {
                        if (tremStart) printf("MusicXML::import: double tremolo start\n");
                        tremStart = static_cast<Chord*>(cr);
                        }
                  else if (tremoloType == "stop") {
                        if (tremStart) {
                              Tremolo* t = new Tremolo(score);
                              if (tremolo == 1) t->setSubtype(3);
                              if (tremolo == 2) t->setSubtype(4);
                              if (tremolo == 3) t->setSubtype(5);
                              t->setChords(tremStart, static_cast<Chord*>(cr));
                              cr->add(t);
                              }
                        else printf("MusicXML::import: double tremolo stop w/o start\n");
                        tremStart = 0;
                        }
                  }
            else
                  printf("unknown tremolo type %d\n", tremolo);
            }

      // more than one dynamic ???
      // LVIFIX: check import/export of <other-dynamics>unknown_text</...>
      // TODO remove duplicate code (see MusicXml::direction)
      for (QStringList::Iterator it = dynamics.begin(); it != dynamics.end(); ++it ) {
            Dynamic* dyn = new Dynamic(score);
            dyn->setSubtype(*it);
            if (hasYoffset) dyn->setYoff(yoffset);
            else dyn->setAbove(placement == "above");
            dyn->setUserOff(QPointF(rx, ry));
            // dyn->setMxmlOff(offset);
            dyn->setTrack(track);
            dyn->setTick(tick);
            measure->add(dyn);
            }
      }

//---------------------------------------------------------
//   xmlNote
//---------------------------------------------------------

/**
 Read a MusicXML note.

 \a Staff is the number of first staff of the part this note belongs to.
 */

void MusicXml::xmlNote(Measure* measure, int staff, QDomElement e)
      {
      int ticks = 0;
      // printf("xmlNote start tick=%d (%d div) divisions=%d\n", tick, tick * divisions / AL::division, divisions);
      QDomNode pn = e; // TODO remove pn
      QDomElement org_e = e; // save e for later
      QDomElement domElemNotations;
      voice = 0;
      move  = 0;
      bool rest    = false;
      int relStaff = 0;
      BeamMode bm  = BEAM_AUTO;
      Direction sd = AUTO;
      int dots     = 0;
      bool grace   = false;
      QString graceSlash;
      QString step;
      int alter  = 0;
      int octave = 4;
      int accidental = 0;
      bool parentheses = false;
      bool editorial = false;
      bool cautionary = false;
      Duration durationType(Duration::V_INVALID);
      int headGroup = 0;
      bool noStem = false;
      QColor noteheadColor = QColor::Invalid;
      bool chord = false;
      int velocity = -1;

      // first read all elements required for voice mapping
      QDomElement e2 = e.firstChildElement();
      for (; !e2.isNull(); e2 = e2.nextSiblingElement()) {
            QString tag(e2.tagName());
            QString s(e2.text());
            if (tag == "voice")
                  voice = s.toInt() - 1;
            else if (tag == "staff")
                  relStaff = s.toInt() - 1;
            else if (tag == "grace") {
                  grace = true;
                  }
            else if (tag == "duration") {
                  ticks = calcTicks(s, divisions);
                  }
            else if (tag == "chord") {
                  chord = true;
                  }
            // silently ignore others (will be handled later)
            }


      // 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.

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

      // printf("voice mapper midway: relStaff=%d voice=%d s=%d v=%d\n", relStaff, voice, s, v);
      if (s < 0 || v < 0) {
            printf("ImportMusicXml: too many voices (staff %d, relStaff %d, voice %d at line %d col %d)\n",
                   staff + 1, relStaff, voice + 1, e.lineNumber(), e.columnNumber());
            return;
            }
      else {
            int d = relStaff - s;
            relStaff = s;
            move += d;
            voice = v;
            }

      // printf("voice mapper after: relStaff=%d move=%d voice=%d\n", relStaff, move, voice);
      // note: relStaff is the staff number relative to the parts first staff
      //       voice is the voice number in the staff

      // for notes that are part of a chord (except the first one)
      // move tick back to the start time of the first note
      if (chord && !grace)
            tick -= lastLen;

      // trk is first track of staff
      int trk = (staff + relStaff) * VOICES;
      // track is current track in staff
      int track = trk + voice;

      QString printObject = "yes";
      if (pn.isElement() && pn.nodeName() == "note") {
            QDomElement pne = pn.toElement();
            printObject = pne.attribute("print-object", "yes");
            }

      velocity = round(e.attribute("dynamics", "-1").toDouble() * 0.9);

      // storage for xmlLyric
      QMap<int, Lyrics*> numberedLyrics; // lyrics with valid number
      QMap<int, Lyrics*> defaultyLyrics; // lyrics with valid default-y
      QList<Lyrics*> unNumberedLyrics;   // lyrics with neither

      for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
            QString tag(e.tagName());
            QString s(e.text());

            if (tag == "pitch") {
                  step   = "C";
                  alter  = 0;
                  octave = 4;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "step")          // A-G
                              step = ee.text();
                        else if (ee.tagName() == "alter") {    // -1=flat 1=sharp (0.5=quarter sharp)
                              bool ok;
                              alter = stringToInt(ee.text(), &ok); // fractions not supported by mscore
                              if (!ok || alter < -2 || alter > 2) {
                                    printf("ImportXml: bad 'alter' value: %s at line %d col %d\n",
                                           qPrintable(ee.text()), ee.lineNumber(), ee.columnNumber());
                                    alter = 0;
                                    }
                              }
                        else if (ee.tagName() == "octave")   // 0-9 4=middle C
                              octave = ee.text().toInt();
                        else
                              domError(ee);
                        }
                  }
            else if (tag == "unpitched") {
                  //
                  // TODO: check semantic
                  //
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "display-step")          // A-G
                              step = ee.text();
                        else if (ee.tagName() == "display-octave")   // 0-9 4=middle C
                              octave = ee.text().toInt();
                        else
                              domError(ee);
                        }
                  }
            else if (tag == "type")
                  durationType = Duration(s);
            else if (tag == "chord" || tag == "duration" || tag == "staff" || tag == "voice")
                  // already handled by voice mapper, ignore here but prevent
                  // spurious "Unknown Node <staff>" or "... <voice>" messages
                  ;
            else if (tag == "stem") {
                  if (s == "up")
                        sd = UP;
                  else if (s == "down")
                        sd = DOWN;
                  else if (s == "none")
                        noStem = true;
                  else if (s == "double")
                        ;
                  else
                        printf("unknown stem direction %s\n", e.text().toLatin1().data());
                  }
            else if (tag == "beam") {
                  int beamNo = e.attribute(QString("number"), "1").toInt();
                  if (beamNo == 1) {
                        if (s == "begin")
                              bm = BEAM_BEGIN;
                        else if (s == "end")
                              bm = BEAM_END;
                        else if (s == "continue")
                              bm = BEAM_MID;
                        else if (s == "backward hook")
                              ;
                        else if (s == "forward hook")
                              ;
                        else
                              printf("unknown beam keyword <%s>\n", s.toLatin1().data());
                        }
                  }
            else if (tag == "rest") {
                  rest = true;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        if (ee.tagName() == "display-step")          // A-G
                              step = ee.text();
                        else if (ee.tagName() == "display-octave")   // 0-9 4=middle C
                              octave = ee.text().toInt();
                        else
                              domError(ee);
                        }
                  }
            else if (tag == "lyric")
                  xmlLyric(track, e, numberedLyrics, defaultyLyrics, unNumberedLyrics);
            else if (tag == "dot")
                  ++dots;
            else if (tag == "accidental") {
                  accidental = convertAccidental(s);
                  if (e.attribute(QString("cautionary")) == "yes")
                        cautionary = true;
                  if (e.attribute(QString("editorial")) == "yes")
                        editorial = true;
                  if (e.attribute(QString("parentheses")) == "yes")
                        parentheses = true;
                  }
            else if (tag == "notations") {
                  // save the QDomElement representing <notations> for later
                  domElemNotations = e;
                  }
            else if (tag == "tie") {
                  QString tieType = e.attribute(QString("type"));
                  if (tieType == "start")
                        ;
                  else if (tieType == "stop")
                        ;
                  else
                        printf("unknown tie type %s\n", tieType.toLatin1().data());
                  }
            else if (tag == "grace") {
                  graceSlash = e.attribute(QString("slash"));
                  }
            else if (tag == "time-modification") {
                  // needed for tuplets, handled in xmlTuplet
                  }
            else if (tag == "notehead") {
                  headGroup = convertNotehead(s);
                  QString color = e.attribute(QString("color"), 0);
                  if (color != 0)
                        noteheadColor = QColor(color);
                  }
            else if (tag == "instrument")
                  domNotImplemented(e);
            else if (tag == "cue")
                  domNotImplemented(e);
            else
                  domError(e);
            }

      // add lyrics found by xmlLyric
      addLyrics(measure, numberedLyrics, defaultyLyrics, unNumberedLyrics);

      // printf("staff=%d relStaff=%d VOICES=%d voice=%d track=%d\n",
      //        staff, relStaff, VOICES, voice, track);

      // printf("%s at %d voice %d dur = %d, beat %d/%d div %d pitch %d ticks %d\n",
      //        rest ? "Rest" : "Note", tick, voice, duration, beats, beatType,
      //        divisions, 0 /* pitch */, ticks);

      // printf("step/alter/oct %s/%d/%d\n", qPrintable(step), alter, octave);

      ChordRest* cr = 0;
      Note* note = 0;

      if (rest) {
            // whole measure rests do not have a "type" element
            if (durationType.type() == Duration::V_INVALID)
                  durationType.setType(Duration::V_MEASURE);
            cr = new Rest(score, tick, durationType);
            cr->setDots(dots);
            if (beamMode == BEAM_BEGIN || beamMode == BEAM_MID)
                  cr->setBeamMode(BEAM_MID);
            else
                  cr->setBeamMode(BEAM_NO);
            cr->setTrack(track);
            cr->setStaffMove(move);
            Segment* s = measure->getSegment(cr);
            s->add(cr);
            cr->setVisible(printObject == "yes");
            if (step != "" && 0 <= octave && octave <= 9) {
                  // printf("rest step=%s oct=%d", qPrintable(step), octave);
                  int clef = cr->staff()->clefList()->clef(tick);
                  int po = clefTable[clef].pitchOffset;
                  int istep = step[0].toAscii() - 'A';
                  // printf(" clef=%d po=%d istep=%d\n", clef, po, istep);
                  if (istep < 0 || istep > 6) {
                        printf("rest: illegal display-step %d, <%s>\n", istep, qPrintable(step));
                        }
                  else {
                        //                        a  b  c  d  e  f  g
                        static int table2[7]  = { 5, 6, 0, 1, 2, 3, 4 };
                        int dp = 7 * (octave + 2) + table2[istep];
                        // printf("dp=%d\n", dp);
                        cr->setUserYoffset((po - dp + 3) * score->spatium() / 2);
                        }
                  }
            }
      else {
            char c = step[0].toLatin1();
            note = new Note(score);
            note->setHeadGroup(headGroup);
            note->setTrack(track);
            if (noteheadColor != QColor::Invalid)
                  note->setColor(noteheadColor);
            // note->setStaffMove(move);

            if (velocity > 0) {
                  note->setVeloType(USER_VAL);
                  note->setVelocity(velocity);
                  }

            // if (grace)
            //       printf(" grace: nrOfGraceSegsReq: %d\n", nrOfGraceSegsReq(pn));
            int gl = nrOfGraceSegsReq(pn);
            cr = measure->findChord(tick, track, grace);
            if (cr == 0) {
                  SegmentType st = SegChordRest;
                  cr = new Chord(score);
                  cr->setTick(tick);
                  cr->setBeamMode(bm);
                  cr->setTrack(track);
                  // printf(" grace=%d\n", grace);
                  if (grace) {
                        NoteType nt = NOTE_APPOGGIATURA;
                        if (graceSlash == "yes")
                              nt = NOTE_ACCIACCATURA;
                        ((Chord*)cr)->setNoteType(nt);
                        // the subtraction causes a grace at tick=0 to fail
                        // cr->setTick(tick - (AL::division / 2));
                        cr->setTick(tick);
                        if (durationType.type() == Duration::V_QUARTER) {
                              ((Chord*)cr)->setNoteType(NOTE_GRACE4);
                              cr->setDuration(Duration::V_QUARTER);
                              }
                        else if (durationType.type() == Duration::V_16TH) {
                              ((Chord*)cr)->setNoteType(NOTE_GRACE16);
                              cr->setDuration(Duration::V_16TH);
                              }
                        else if (durationType.type() == Duration::V_32ND) {
                              ((Chord*)cr)->setNoteType(NOTE_GRACE32);
                              cr->setDuration(Duration::V_32ND);
                              }
                        else
                              cr->setDuration(Duration::V_EIGHT);
                        st = SegGrace;
                        }
                  else {
                        if (durationType.type() == Duration::V_INVALID)
                              durationType.setType(Duration::V_QUARTER);
                        cr->setDuration(durationType);
                        }
                  cr->setDots(dots);
                  // printf(" cr->tick()=%d ", cr->tick());
                  Segment* s = measure->getSegment(st, cr->tick(), gl);
                  s->add(cr);
                  }
            // printf(" cr->tick()=%d", cr->tick());
            cr->setStaffMove(move);

            // pitch must be set before adding note to chord as note
            // is inserted into pitch sorted list (ws)

            xmlSetPitch(note, c, alter, octave, ottava, track);
            cr->add(note);

            ((Chord*)cr)->setNoStem(noStem);

            // printf("staff for new note: %p (staff=%d, relStaff=%d)\n",
            //        score->staff(staff + relStaff), staff, relStaff);

            // Add regular accidentals with attribute editorial or cautionary or parentheses
            // as user accidental (force presence)
            if (ACC_SHARP <= accidental && accidental <= ACC_NATURAL && (editorial || cautionary || parentheses))
                  note->setUserAccidental(accidental + ((cautionary || parentheses) ? 0x8000 : 0));
            // Similar for quarter tone accidentals
            // Note: support is "drawing only"
            if (accidental > ACC_NATURAL && accidental < ACC_END)
                  note->setUserAccidental(accidental + ((cautionary || parentheses) ? 0x8000 : 0));

            if (cr->beamMode() == BEAM_NO)
                  cr->setBeamMode(bm);
            // remember beam mode last non-grace note
            // bm == BEAM_AUTO means no <beam> was found
            if (!grace && bm != BEAM_AUTO)
                  beamMode = bm;
            ((Chord*)cr)->setStemDirection(sd);

            note->setVisible(printObject == "yes");
            }

      if (!chord && !grace) {
            xmlTuplet(tuplets[voice + relStaff * VOICES], cr, ticks, org_e);
            }

      if (!domElemNotations.isNull())
            xmlNotations(note, cr, trk, ticks, domElemNotations);
      }

//---------------------------------------------------------
//   addWedge
//---------------------------------------------------------

/**
 Add a MusicXML wedge to the wedge list.

 Called when the wedge start is read. Stores all wedge parameters known at this time.
 */

void MusicXml::addWedge(int no, int startTick, qreal rx, qreal ry, bool above, bool hasYoffset, qreal yoffset, int subType)
      {
      MusicXmlWedge wedge;
      wedge.number = no;
      wedge.startTick = startTick;
      wedge.rx = rx;
      wedge.ry = ry;
      wedge.above = above;
      wedge.hasYoffset = hasYoffset;
      wedge.yoffset = yoffset;
      wedge.subType = subType;

      if (int(wedgeList.size()) > no)
            wedgeList[no] = wedge;
      else
            wedgeList.push_back(wedge);
      }

//---------------------------------------------------------
//   genWedge
//---------------------------------------------------------

/**
 Add a MusicXML wedge to the score.

 Called when the wedge stop is read. Wedge stop tick was unknown until this time.
 */

void MusicXml::genWedge(int no, int endTick, Measure* /*measure*/, int staff)
      {
      Hairpin* hp = new Hairpin(score);

      hp->setTick(wedgeList[no].startTick);
      hp->setTick2(endTick);
      hp->setSubtype(wedgeList[no].subType);
      if (wedgeList[no].hasYoffset) hp->setYoff(wedgeList[no].yoffset);
      else hp->setYoff(wedgeList[no].above ? -3 : 8);
      hp->setUserOff(QPointF(wedgeList[no].rx, wedgeList[no].ry));
      hp->setTrack(staff * VOICES);
      if (hp->check())
            score->add(hp);

      // printf("gen wedge %p staff %d, tick %d-%d\n", hp, staff, hp->tick(), hp->tick2());
      }

//---------------------------------------------------------
//   xmlHarmony
//---------------------------------------------------------

void MusicXml::xmlHarmony(QDomElement e, int tick, Measure* measure, int staff)
      {
      // type:

      // placement:
      double rx = e.attribute("relative-x", "0").toDouble()*0.1;
      double ry = e.attribute("relative-y", "0").toDouble()* -0.1;

      int relStaff = 0;

      double styleYOff = score->textStyle(TEXT_STYLE_HARMONY)->yoff;
      OffsetType offsetType = score->textStyle(TEXT_STYLE_HARMONY)->offsetType;
      if (offsetType == OFFSET_ABS) {
            styleYOff = styleYOff * DPMM / score->spatium();
            }

      double dy = e.attribute("default-y", QString::number(styleYOff* -10)).toDouble()* -0.1;

      QString printObject(e.attribute("print-object", "yes"));
      QString printFrame(e.attribute("print-frame"));
      QString printStyle(e.attribute("print-style"));

      QString kind, kindText;
      QList<HDegree> degreeList;

      Harmony* ha = new Harmony(score);
      ha->setUserOff(QPointF(rx, ry + dy - styleYOff));
      int offset = 0;
      for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
            QString tag(e.tagName());
            if (tag == "root") {
                  QString step;
                  int alter = 0;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        QString tag(ee.tagName());
                        if (tag == "root-step") {
                              // attributes: print-style
                              step = ee.text();
                              }
                        else if (tag == "root-alter") {
                              // attributes: print-object, print-style
                              //             location (left-right)
                              alter = ee.text().toInt();
                              }
                        else
                              domError(ee);
                        }
                  ha->setRootTpc(step2tpc(step, alter));
                  }
            else if (tag == "function") {
                  // attributes: print-style
                  domNotImplemented(e);
                  }
            else if (tag == "kind") {
                  // attributes: use-symbols  yes-no
                  //             text, stack-degrees, parentheses-degree, bracket-degrees,
                  //             print-style, halign, valign

                  kindText = e.attribute("text");
                  kind = e.text();
                  }
            else if (tag == "inversion") {
                  // attributes: print-style
                  }
            else if (tag == "bass") {
                  QString step;
                  int alter = 0;
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        QString tag(ee.tagName());
                        if (tag == "bass-step") {
                              // attributes: print-style
                              step = ee.text();
                              }
                        else if (tag == "bass-alter") {
                              // attributes: print-object, print-style
                              //             location (left-right)
                              alter = ee.text().toInt();
                              }
                        else
                              domError(ee);
                        }
                  ha->setBaseTpc(step2tpc(step, alter));
                  }
            else if (tag == "degree") {
                  int degreeValue = 0;
                  int degreeAlter = 0;
                  QString degreeType = "";
                  for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
                        QString tag(ee.tagName());
                        if (tag == "degree-value") {
                              degreeValue = ee.text().toInt();
                              }
                        else if (tag == "degree-alter") {
                              degreeAlter = ee.text().toInt();
                              }
                        else if (tag == "degree-type") {
                              degreeType = ee.text();
                              }
                        else
                              domError(ee);
                        }
                  if (degreeValue <= 0 || degreeValue > 13
                      || degreeAlter < -2 || degreeAlter > 2
                      || (degreeType != "add" && degreeType != "alter" && degreeType != "subtract")) {
                        printf("incorrect degree: degreeValue=%d degreeAlter=%d degreeType=%s\n",
                               degreeValue, degreeAlter, qPrintable(degreeType));
                        }
                  else {
                        if (degreeType == "add")
                              degreeList << HDegree(degreeValue, degreeAlter, ADD);
                        else if (degreeType == "alter")
                              degreeList << HDegree(degreeValue, degreeAlter, ALTER);
                        else if (degreeType == "subtract")
                              degreeList << HDegree(degreeValue, degreeAlter, SUBTRACT);
                        }
                  }
            else if (tag == "level") {
                  domNotImplemented(e);
                  }
            else if (tag == "offset") {
                  offset = (e.text().toInt() * AL::division)/divisions;
                  }
            else if (tag == "staff") {
                  relStaff = e.text().toInt() - 1;
                  }
            else
                  domError(e);
            }

      ha->setTick(tick + offset);
      ha->setTrack((staff + relStaff) * VOICES);
      const ChordDescription* d = ha->fromXml(kind, degreeList);
      if (d == 0) {
            QString degrees;
            foreach(const HDegree &d, degreeList) {
                  if (!degrees.isEmpty())
                        degrees += " ";
                  degrees += d.text();
                  }
            printf("unknown chord txt: <%s> kind: <%s> degrees: %s\n",
                   qPrintable(kindText), qPrintable(kind), qPrintable(degrees));

            // Strategy II: lookup "kind", merge in degree list and try to find
            //    harmony in list

            d = ha->fromXml(kind);
            if (d) {
                  ha->setId(d->id);
                  foreach (const HDegree &d, degreeList) {
                        ha->addDegree(d);
                        }
                  ha->resolveDegreeList();
                  ha->render();
                  }
            else {
                  printf("'kind: <%s> not found in harmony data base\n", qPrintable(kind));
                  QString s = tpc2name(ha->rootTpc(), score->style(ST_useGermanNoteNames).toBool()) + kindText;
                  ha->setText(s);
                  }
            }
      else {
            ha->setId(d->id);
            // ha->resolveDegreeList();
            ha->render();
            }
      ha->setVisible(printObject == "yes");
      measure->add(ha);
      }

//---------------------------------------------------------
//   xmlClef
//---------------------------------------------------------

void MusicXml::xmlClef(QDomElement e, int staffIdx, Measure* measure)
      {
      int clef   = 0;
      int clefno = e.attribute(QString("number"), "1").toInt() - 1;
      QString c;
      int i = 0;
      int line = -1;
      for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
            if (ee.tagName() == "sign")
                  c = ee.text();
            else if (ee.tagName() == "line")
                  line = ee.text().toInt();
            else if (ee.tagName() == "clef-octave-change") {
                  i = ee.text().toInt();
                  if (i && !(c == "F" || c == "G"))
                        printf("clef-octave-change only implemented for F and G key\n");
                  }
            else
                  domError(ee);
            }
      //some software (Primus) don't include line and assume some default
      // it's permitted by MusicXML 2.0 XSD
      if (line == -1) {
            if (c == "G")
                  line = 2;
            else if (c == "F")
                  line = 4;
            else if (c == "C")
                  line = 3;
            }

      if (c == "G" && i == 0 && line == 2)
            clef = CLEF_G;
      else if (c == "G" && i == 1 && line == 2)
            clef = CLEF_G1;
      else if (c == "G" && i == 2 && line == 2)
            clef = CLEF_G2;
      else if (c == "G" && i == -1 && line == 2)
            clef = CLEF_G3;
      else if (c == "G" && i == 0 && line == 1)
            clef = CLEF_G4;
      else if (c == "F" && i == 0 && line == 3)
            clef = CLEF_F_B;
      else if (c == "F" && i == 0 && line == 4)
            clef = CLEF_F;
      else if (c == "F" && i == 1 && line == 4)
            clef = CLEF_F_8VA;
      else if (c == "F" && i == 2 && line == 4)
            clef = CLEF_F_15MA;
      else if (c == "F" && i == -1 && line == 4)
            clef = CLEF_F8;
      else if (c == "F" && i == -2 && line == 4)
            clef = CLEF_F15;
      else if (c == "F" && i == 0 && line == 5)
            clef = CLEF_F_C;
      else if (c == "C") {
            if (line == 5)
                  clef = CLEF_C5;
            else if (line == 4)
                  clef = CLEF_C4;
            else if (line == 3)
                  clef = CLEF_C3;
            else if (line == 2)
                  clef = CLEF_C2;
            else if (line == 1)
                  clef = CLEF_C1;
            }
      else if (c == "percussion")
            clef = CLEF_PERC;
      else
            printf("ImportMusicXML: unknown clef <sign=%s line=%d oct ch=%d>\n", qPrintable(c), line, i);
      Staff* part = score->staff(staffIdx + clefno);
      ClefList* ct = part->clefList();
      (*ct)[tick] = clef;
      if (tick) {
            // dont generate symbol for tick 0
            Clef* clefs = new Clef(score, clef);
            clefs->setTick(tick);
            clefs->setTrack((staffIdx + clefno) * VOICES);
            Segment* s = measure->getSegment(clefs);
            s->add(clefs);
            ++clefno;   // ??
            }
      }

