    //=============================================================================
    //  MuseScore
    //  Music Composition & Notation
    //
    //  Copyright (C) 2008-2011 Werner Schweer
    //
    //  This program is free software; you can redistribute it and/or modify
    //  it under the terms of the GNU General Public License version 2
    //  as published by the Free Software Foundation and appearing in
    //  the file LICENCE.GPL
    //=============================================================================

    #include "harmony.h"
    #include "pitchspelling.h"
    #include "score.h"
    #include "system.h"
    #include "measure.h"
    #include "segment.h"
    #include "chordlist.h"
    #include "mscore.h"
    #include "fret.h"
    #include "staff.h"
    #include "part.h"
    #include "utils.h"
    #include "sym.h"
    #include "xml.h"

    namespace Ms {

    //---------------------------------------------------------
    //   harmonyName
    //---------------------------------------------------------

    QString Harmony::harmonyName() const
          {
          // Hack:
          const_cast<Harmony*>(this)->determineRootBaseSpelling();

          HChord hc = descr() ? descr()->chord : HChord();
          QString s, r, e, b;

          if (_leftParen)
                s = "(";

          if (_rootTpc != Tpc::TPC_INVALID)
                r = tpc2name(_rootTpc, _rootSpelling, _rootCase);

          if (_textName != "") {
                e = _textName;
                e.remove('=');
                }
          else if (!_degreeList.empty()) {
                hc.add(_degreeList);
                // try to find the chord in chordList
                const ChordDescription* newExtension = 0;
                const ChordList* cl = score()->style().chordList();
                for (const ChordDescription& cd : *cl) {
                      if (cd.chord == hc && !cd.names.empty()) {
                            newExtension = &cd;
                            break;
                            }
                      }
                // now determine the chord name
                if (newExtension)
                      e = newExtension->names.front();
                else {
                      // not in table, fallback to using HChord.name()
                      r = hc.name(_rootTpc);
                      e = "";
                      }
                }

          if (_baseTpc != Tpc::TPC_INVALID)
                b = "/" + tpc2name(_baseTpc, _baseSpelling, _baseCase);

          s += r + e + b;

          if (_rightParen)
                s += ")";

          return s;
          }

    //---------------------------------------------------------
    //   rootName
    //---------------------------------------------------------

    QString Harmony::rootName()
          {
          determineRootBaseSpelling();
          return tpc2name(_rootTpc, _rootSpelling, _rootCase);
          }

    //---------------------------------------------------------
    //   baseName
    //---------------------------------------------------------

    QString Harmony::baseName()
          {
          determineRootBaseSpelling();
          return tpc2name(_baseTpc, _baseSpelling, _baseCase);
          }

    //---------------------------------------------------------
    //   resolveDegreeList
    //    try to detect chord number and to eliminate degree
    //    list
    //---------------------------------------------------------

    void Harmony::resolveDegreeList()
          {
          if (_degreeList.empty())
                return;

          HChord hc = descr() ? descr()->chord : HChord();

          hc.add(_degreeList);

    // qDebug("resolveDegreeList: <%s> <%s-%s>: ", _descr->name, _descr->xmlKind, _descr->xmlDegrees);
    // hc.print();
    // _descr->chord.print();

          // try to find the chord in chordList
          const ChordList* cl = score()->style().chordList();
          foreach(const ChordDescription& cd, *cl) {
                if ((cd.chord == hc) && !cd.names.empty()) {
    qDebug("ResolveDegreeList: found in table as %s", qPrintable(cd.names.front()));
                      _id = cd.id;
                      _degreeList.clear();
                      return;
                      }
                }
    qDebug("ResolveDegreeList: not found in table");
          }

    //---------------------------------------------------------
    //   Harmony
    //---------------------------------------------------------

    Harmony::Harmony(Score* s)
       : Text(SubStyle::HARMONY, s)
          {
          _rootTpc    = Tpc::TPC_INVALID;
          _baseTpc    = Tpc::TPC_INVALID;
          _rootCase   = NoteCaseType::CAPITAL;
          _baseCase   = NoteCaseType::CAPITAL;
          _id         = -1;
          _parsedForm = 0;
          _leftParen  = false;
          _rightParen = false;
          setFlags(ElementFlag::MOVABLE | ElementFlag::SELECTABLE | ElementFlag::ON_STAFF);
          }

    Harmony::Harmony(const Harmony& h)
       : Text(h)
          {
          _rootTpc    = h._rootTpc;
          _baseTpc    = h._baseTpc;
          _rootCase   = h._rootCase;
          _baseCase   = h._baseCase;
          _id         = h._id;
          _leftParen  = h._leftParen;
          _rightParen = h._rightParen;
          _degreeList = h._degreeList;
          _parsedForm = h._parsedForm ? new ParsedChord(*h._parsedForm) : 0;
          _textName   = h._textName;
          _userName   = h._userName;
          foreach(const TextSegment* s, h.textList) {
                TextSegment* ns = new TextSegment();
                ns->set(s->text, s->font, s->x, s->y);
                textList.append(ns);
                }
          }

    //---------------------------------------------------------
    //   ~Harmony
    //---------------------------------------------------------

    Harmony::~Harmony()
          {
          foreach(const TextSegment* ts, textList)
                delete ts;
          if (_parsedForm)
                delete _parsedForm;
          }

    //---------------------------------------------------------
    //   write
    //---------------------------------------------------------

    void Harmony::write(XmlWriter& xml) const
          {
          if (!xml.canWrite(this))
                return;
          xml.stag("Harmony");
          if (_leftParen)
                xml.tagE("leftParen");
          if (_rootTpc != Tpc::TPC_INVALID || _baseTpc != Tpc::TPC_INVALID) {
                int rRootTpc = _rootTpc;
                int rBaseTpc = _baseTpc;
                if (staff()) {
                      Segment* segment = static_cast<Segment*>(parent());
                      int tick = segment ? segment->tick() : -1;
                      const Interval& interval = part()->instrument(tick)->transpose();
                      if (xml.clipboardmode() && !score()->styleB(StyleIdx::concertPitch) && interval.chromatic) {
                            rRootTpc = transposeTpc(_rootTpc, interval, true);
                            rBaseTpc = transposeTpc(_baseTpc, interval, true);
                            }
                      }
                if (rRootTpc != Tpc::TPC_INVALID) {
                      xml.tag("root", rRootTpc);
                      if (_rootCase != NoteCaseType::CAPITAL)
                            xml.tag("rootCase", static_cast<int>(_rootCase));
                      }
                if (_id > 0)
                      xml.tag("extension", _id);
                // parser uses leading "=" as a hidden specifier for minor
                // this may or may not currently be incorporated into _textName
                QString writeName = _textName;
                if (_parsedForm && _parsedForm->name().startsWith("=") && !writeName.startsWith("="))
                      writeName = "=" + writeName;
                if (writeName != "")
                      xml.tag("name", writeName);

                if (rBaseTpc != Tpc::TPC_INVALID) {
                      xml.tag("base", rBaseTpc);
                      if (_baseCase != NoteCaseType::CAPITAL)
                            xml.tag("baseCase", static_cast<int>(_baseCase));
                      }
                foreach(const HDegree& hd, _degreeList) {
                      HDegreeType tp = hd.type();
                      if (tp == HDegreeType::ADD || tp == HDegreeType::ALTER || tp == HDegreeType::SUBTRACT) {
                            xml.stag("degree");
                            xml.tag("degree-value", hd.value());
                            xml.tag("degree-alter", hd.alter());
                            switch (tp) {
                                  case HDegreeType::ADD:
                                        xml.tag("degree-type", "add");
                                        break;
                                  case HDegreeType::ALTER:
                                        xml.tag("degree-type", "alter");
                                        break;
                                  case HDegreeType::SUBTRACT:
                                        xml.tag("degree-type", "subtract");
                                        break;
                                  default:
                                        break;
                                  }
                            xml.etag();
                            }
                      }
                }
          else
                xml.tag("name", _textName);
          Text::writeProperties(xml, false, true);
          if (_rightParen)
                xml.tagE("rightParen");
          xml.etag();
          }

    //---------------------------------------------------------
    //   read
    //---------------------------------------------------------

    void Harmony::read(XmlReader& e)
          {
          // convert table to tpc values
          static const int table[] = {
                14, 9, 16, 11, 18, 13, 8, 15, 10, 17, 12, 19
                };

          while (e.readNextStartElement()) {
                const QStringRef& tag(e.name());
                if (tag == "base") {
                      if (score()->mscVersion() >= 106)
                            setBaseTpc(e.readInt());
                      else
                            setBaseTpc(table[e.readInt()-1]);    // obsolete
                      }
                else if (tag == "baseCase")
                      _baseCase = static_cast<NoteCaseType>(e.readInt());
                else if (tag == "extension")
                      setId(e.readInt());
                else if (tag == "name")
                      _textName = e.readElementText();
                else if (tag == "root") {
                      if (score()->mscVersion() >= 106)
                            setRootTpc(e.readInt());
                      else
                            setRootTpc(table[e.readInt()-1]);    // obsolete
                      }
                else if (tag == "rootCase")
                      _rootCase = static_cast<NoteCaseType>(e.readInt());
                else if (tag == "degree") {
                      int degreeValue = 0;
                      int degreeAlter = 0;
                      QString degreeType = "";
                      while (e.readNextStartElement()) {
                            const QStringRef& tag(e.name());
                            if (tag == "degree-value")
                                  degreeValue = e.readInt();
                            else if (tag == "degree-alter")
                                  degreeAlter = e.readInt();
                            else if (tag == "degree-type")
                                  degreeType = e.readElementText();
                            else
                                  e.unknown();
                            }
                      if (degreeValue <= 0 || degreeValue > 13
                          || degreeAlter < -2 || degreeAlter > 2
                          || (degreeType != "add" && degreeType != "alter" && degreeType != "subtract")) {
                            qDebug("incorrect degree: degreeValue=%d degreeAlter=%d degreeType=%s",
                                   degreeValue, degreeAlter, qPrintable(degreeType));
                            }
                      else {
                            if (degreeType == "add")
                                  addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ADD));
                            else if (degreeType == "alter")
                                  addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::ALTER));
                            else if (degreeType == "subtract")
                                  addDegree(HDegree(degreeValue, degreeAlter, HDegreeType::SUBTRACT));
                            }
                      }
                else if (tag == "leftParen") {
                      _leftParen = true;
                      e.readNext();
                      }
                else if (tag == "rightParen") {
                      _rightParen = true;
                      e.readNext();
                      }
                else if (!Text::readProperties(e))
                      e.unknown();
                }

          // TODO: now that we can render arbitrary chords,
          // we could try to construct a full representation from a degree list.
          // These will typically only exist for chords imported from MusicXML prior to MuseScore 2.0
          // or constructed in the Chord Symbol Properties dialog.

          if (_rootTpc != Tpc::TPC_INVALID) {
                if (_id > 0) {
                      // positive id will happen only for scores that were created with explicit chord lists
                      // lookup id in chord list and generate new description if necessary
                      getDescription();
                      }
                else
                      {
                      // default case: look up by name
                      // description will be found for any chord already read in this score
                      // and we will generate a new one if necessary
                      getDescription(_textName);
                      }
                }
          else if (_textName == "") {
                // unrecognized chords prior to 2.0 were stored as text with markup
                // we need to strip away the markup
                // this removes any user-applied formatting,
                // but we no longer support user-applied formatting for chord symbols anyhow
                // with any luck, the resulting text will be parseable now, so give it a shot
                createLayout();
                QString s = plainText();
                if (!s.isEmpty()) {
                      setHarmony(s);
                      return;
                      }
                // empty text could also indicate a root-less slash chord ("/E")
                // we'll fall through and render it normally
                }

          // render chord from description (or _textName)
          render();
          }

    //---------------------------------------------------------
    //   determineRootBaseSpelling
    //---------------------------------------------------------

    void Harmony::determineRootBaseSpelling(NoteSpellingType& rootSpelling, NoteCaseType& rootCase,
       NoteSpellingType& baseSpelling, NoteCaseType& baseCase)
          {
          // spelling
          if (score()->styleB(StyleIdx::useStandardNoteNames))
                rootSpelling = NoteSpellingType::STANDARD;
          else if (score()->styleB(StyleIdx::useGermanNoteNames))
                rootSpelling = NoteSpellingType::GERMAN;
          else if (score()->styleB(StyleIdx::useFullGermanNoteNames))
                rootSpelling = NoteSpellingType::GERMAN_PURE;
          else if (score()->styleB(StyleIdx::useSolfeggioNoteNames))
                rootSpelling = NoteSpellingType::SOLFEGGIO;
          else if (score()->styleB(StyleIdx::useFrenchNoteNames))
                rootSpelling = NoteSpellingType::FRENCH;
          baseSpelling = rootSpelling;

          // case

          // always use case as typed if automatic capitalization is off
          if (!score()->styleB(StyleIdx::automaticCapitalization)) {
                rootCase = _rootCase;
                baseCase = _baseCase;
                return;
                }

          // set default
          if (score()->styleB(StyleIdx::allCapsNoteNames)) {
                rootCase = NoteCaseType::UPPER;
                baseCase = NoteCaseType::UPPER;
                }
          else {
                rootCase = NoteCaseType::CAPITAL;
                baseCase = NoteCaseType::CAPITAL;
                }

          // override for bass note
          if (score()->styleB(StyleIdx::lowerCaseBassNotes))
                baseCase = NoteCaseType::LOWER;

          // override for minor chords
          if (score()->styleB(StyleIdx::lowerCaseMinorChords)) {
                const ChordDescription* cd = descr();
                QString quality;
                if (cd) {
                      // use chord description if possible
                      // this is the usual case
                      quality = cd->quality();
                      }
                else if (_parsedForm) {
                      // this happens on load of new chord list
                      // for chord symbols that were added/edited since the score was loaded
                      // or read aloud with screenreader
                      // parsed form is usable even if out of date with respect to chord list
                      quality = _parsedForm->quality();
                      }
                else {
                      // this happens on load of new chord list
                      // for chord symbols that have not been edited since the score was loaded
                      // we need to parse this chord for now to determine quality
                      // but don't keep the parsed form around as we're not ready for it yet
                      quality = parsedForm()->quality();
                      delete _parsedForm;
                      _parsedForm = 0;
                      }
                if (quality == "minor" || quality == "diminished" || quality == "half-diminished")
                      rootCase = NoteCaseType::LOWER;
                }
          }

    //---------------------------------------------------------
    //   determineRootBaseSpelling
    //---------------------------------------------------------

    void Harmony::determineRootBaseSpelling()
          {
          determineRootBaseSpelling(_rootSpelling, _rootRenderCase,
            _baseSpelling, _baseRenderCase);
          }

    //---------------------------------------------------------
    //   convertNote
    //    convert something like "C#" into tpc 21
    //---------------------------------------------------------

    static int convertNote(const QString& s, NoteSpellingType noteSpelling, NoteCaseType& noteCase, int& idx)
          {
          bool useGerman = false;
          bool useSolfeggio = false;
          static const int spellings[] = {
             // bb  b   -   #  ##
                0,  7, 14, 21, 28,  // C
                2,  9, 16, 23, 30,  // D
                4, 11, 18, 25, 32,  // E
               -1,  6, 13, 20, 27,  // F
                1,  8, 15, 22, 29,  // G
                3, 10, 17, 24, 31,  // A
                5, 12, 19, 26, 33,  // B
                };
          if (s == "")
                return Tpc::TPC_INVALID;
          noteCase = s[0].isLower() ? NoteCaseType::LOWER : NoteCaseType::CAPITAL;
          int acci;
          switch (noteSpelling) {
                case NoteSpellingType::SOLFEGGIO:
                case NoteSpellingType::FRENCH:
                      useSolfeggio = true;
                      if (s.toLower().startsWith("sol"))
                            acci = 3;
                      else
                            acci = 2;
                      break;
                case NoteSpellingType::GERMAN:
                case NoteSpellingType::GERMAN_PURE:
                      useGerman = true;
                      // fall through
                default:
                      acci = 1;
                }
          idx = acci;
          int alter = 0;
          int n = s.size();
          QString acc = s.right(n-acci);
          if (acc != "") {
                if (acc.startsWith("bb")) {
                      alter = -2;
                      idx += 2;
                      }
                else if (acc.startsWith("b")) {
                      alter = -1;
                      idx += 1;
                      }
                else if (useGerman && acc.startsWith("eses")) {
                      alter = -2;
                      idx += 4;
                      }
                else if (useGerman && (acc.startsWith("ses") || acc.startsWith("sas"))) {
                      alter = -2;
                      idx += 3;
                      }
                else if (useGerman && acc.startsWith("es")) {
                      alter = -1;
                      idx += 2;
                      }
                else if (useGerman && acc.startsWith("s") && !acc.startsWith("su")) {
                      alter = -1;
                      idx += 1;
                      }
                else if (acc.startsWith("##")) {
                      alter = 2;
                      idx += 2;
                      }
                else if (acc.startsWith("x")) {
                      alter = 2;
                      idx += 1;
                      }
                else if (acc.startsWith("#")) {
                      alter = 1;
                      idx += 1;
                      }
                else if (useGerman && acc.startsWith("isis")) {
                      alter = 2;
                      idx += 4;
                      }
                else if (useGerman && acc.startsWith("is")) {
                      alter = 1;
                      idx += 2;
                      }
                }
          int r;
          if (useGerman) {
                switch(s[0].toLower().toLatin1()) {
                      case 'c':   r = 0; break;
                      case 'd':   r = 1; break;
                      case 'e':   r = 2; break;
                      case 'f':   r = 3; break;
                      case 'g':   r = 4; break;
                      case 'a':   r = 5; break;
                      case 'h':   r = 6; break;
                      case 'b':
                            if (alter && alter != -1)
                                  return Tpc::TPC_INVALID;
                            r = 6;
                            alter = -1;
                            break;
                      default:
                            return Tpc::TPC_INVALID;
                      }
                }
          else if (useSolfeggio) {
                if (s.length() < 2)
                      return Tpc::TPC_INVALID;
                if (s[1].isUpper())
                      noteCase = NoteCaseType::UPPER;
                QString ss = s.toLower().left(2);
                if (ss == "do")
                      r = 0;
                else if (ss == "re" || ss == "ré")
                      r = 1;
                else if (ss == "mi")
                      r = 2;
                else if (ss == "fa")
                      r = 3;
                else if (ss == "so")    // sol, but only check first 2 characters
                      r = 4;
                else if (ss == "la")
                      r = 5;
                else if (ss == "si")
                      r = 6;
                else
                      return Tpc::TPC_INVALID;
                }
          else {
                switch(s[0].toLower().toLatin1()) {
                      case 'c':   r = 0; break;
                      case 'd':   r = 1; break;
                      case 'e':   r = 2; break;
                      case 'f':   r = 3; break;
                      case 'g':   r = 4; break;
                      case 'a':   r = 5; break;
                      case 'b':   r = 6; break;
                      default:    return Tpc::TPC_INVALID;
                      }
                }
          r = spellings[r * 5 + alter + 2];
          return r;
          }

    //---------------------------------------------------------
    //   parseHarmony
    //    determine root and bass tpc & case
    //    compare body of chordname against chord list
    //    return true if chord is recognized
    //---------------------------------------------------------

    const ChordDescription* Harmony::parseHarmony(const QString& ss, int* root, int* base, bool syntaxOnly)
          {
          _id = -1;
          if (_parsedForm) {
                delete _parsedForm;
                _parsedForm = 0;
                }
          _textName = "";
          bool useLiteral = false;
          if (ss.endsWith(' '))
                useLiteral = true;
          QString s = ss.simplified();

          if ((_leftParen = s.startsWith('(')))
                s.remove(0,1);

          if ((_rightParen = (s.endsWith(')') && s.count('(') < s.count(')'))))
                s.remove(s.size()-1,1);

          if (_leftParen || _rightParen)
                s = s.simplified();     // in case of spaces inside parentheses

          int n = s.size();
          if (n < 1)
                return 0;
          determineRootBaseSpelling();
          int idx;
          int r = convertNote(s, _rootSpelling, _rootCase, idx);
          if (r == Tpc::TPC_INVALID) {
                if (s[0] == '/')
                      idx = 0;
                else {
                      qDebug("1:parseHarmony failed <%s>", qPrintable(ss));
                      _userName = s;
                      _textName = s;
                      return 0;
                      }
                }
          *root = r;
          bool preferMinor;
          if (score()->styleB(StyleIdx::lowerCaseMinorChords) && s[0].isLower())
                preferMinor = true;
          else
                preferMinor = false;
          *base = Tpc::TPC_INVALID;
          int slash = s.lastIndexOf('/');
          if (slash != -1) {
                QString bs = s.mid(slash + 1).simplified();
                s = s.mid(idx, slash - idx).simplified();
                int idx2;
                *base = convertNote(bs, _baseSpelling, _baseCase, idx2);
                if (idx2 != bs.size())
                      *base = Tpc::TPC_INVALID;
                if (*base == Tpc::TPC_INVALID) {
                      // if what follows after slash is not (just) a TPC
                      // then reassemble chord and try to parse with the slash
                      s = s + "/" + bs;
                      }
                }
          else
                s = s.mid(idx);   // don't simplify; keep leading space before extension if present
          _userName = s;
          const ChordList* cl = score()->style().chordList();
          const ChordDescription* cd = 0;
          if (useLiteral)
                cd = descr(s);
          else {
                _parsedForm = new ParsedChord();
                _parsedForm->parse(s, cl, syntaxOnly, preferMinor);
                // parser prepends "=" to name of implied minor chords
                // use this here as well
                if (preferMinor)
                      s = _parsedForm->name();
                // look up to see if we already have a descriptor (chord has been used before)
                cd = descr(s, _parsedForm);
                }
          if (cd) {
                // descriptor found; use its information
                _id = cd->id;
                if (!cd->names.empty())
                      _textName = cd->names.front();
                }
          else {
                // no descriptor yet; just set textname
                // we will generate descriptor later if necessary (when we are done editing this chord)
                _textName = s;
                }
          return cd;
          }

    //---------------------------------------------------------
    //   startEdit
    //---------------------------------------------------------

    void Harmony::startEdit(EditData& ed)
          {
          if (!textList.empty())
                setXmlText(harmonyName());
          Text::startEdit(ed);
          layout();
          }

    //---------------------------------------------------------
    //   edit
    //---------------------------------------------------------

    bool Harmony::edit(EditData& ed)
          {
          if (ed.key == Qt::Key_Return)
                return true; // Harmony only single line
          bool rv = Text::edit(ed);
          QString str = xmlText();
          int root, base;
          bool badSpell = !str.isEmpty() && !parseHarmony(str, &root, &base, true);
          spellCheckUnderline(badSpell);
          return rv;
          }

    //---------------------------------------------------------
    //   endEdit
    //---------------------------------------------------------

    void Harmony::endEdit(EditData& ed)
          {
          Text::endEdit(ed);
          layout();
          if (links()) {
                foreach(ScoreElement* e, *links()) {
                      if (e == this)
                            continue;
                      Harmony* h = static_cast<Harmony*>(e);
                      // transpose if necessary
                      // at this point chord will already have been rendered in same key as original
                      // (as a result of Text::endEdit() calling setText() for linked elements)
                      // we may now need to change the TPC's and the text, and re-render
                      if (score()->styleB(StyleIdx::concertPitch) != h->score()->styleB(StyleIdx::concertPitch)) {
                            Part* partDest = h->part();
                            Segment* segment = static_cast<Segment*>(parent());
                            int tick = segment ? segment->tick() : -1;
                            Interval interval = partDest->instrument(tick)->transpose();
                            if (!interval.isZero()) {
                                  if (!h->score()->styleB(StyleIdx::concertPitch))
                                        interval.flip();
                                  int rootTpc = transposeTpc(h->rootTpc(), interval, true);
                                  int baseTpc = transposeTpc(h->baseTpc(), interval, true);
                                  //score()->undoTransposeHarmony(h, rootTpc, baseTpc);
                                  h->setRootTpc(rootTpc);
                                  h->setBaseTpc(baseTpc);
                                  // this invokes textChanged(), which handles the rendering
                                  h->setXmlText(h->harmonyName());
                                  }
                            }
                      }
                }
          score()->setLayoutAll();
          }

    #if 0
    //---------------------------------------------------------
    //   setTextStyle
    //---------------------------------------------------------

    void Harmony::setTextStyle(const TextStyle& st)
          {
          Text::setTextStyle(st);
          render();
          }
    #endif

    //---------------------------------------------------------
    //   setHarmony
    //---------------------------------------------------------

    void Harmony::setHarmony(const QString& s)
          {
          int r, b;
          const ChordDescription* cd = parseHarmony(s, &r, &b);
          if (!cd && _parsedForm && _parsedForm->parseable()) {
                // our first time encountering this chord
                // generate a descriptor and use it
                cd = generateDescription();
                _id = cd->id;
                }
          if (cd) {
                setRootTpc(r);
                setBaseTpc(b);
                render();
                }
          else {
                // unparseable chord, render as plain text
                foreach(const TextSegment* s, textList)
                      delete s;
                textList.clear();
                setRootTpc(Tpc::TPC_INVALID);
                setBaseTpc(Tpc::TPC_INVALID);
                _id = -1;
                render();
                }
          }

    //---------------------------------------------------------
    //   baseLine
    //---------------------------------------------------------

    qreal Harmony::baseLine() const
          {
    //      return (editMode() || textList.empty()) ? Text::baseLine() : 0.0;
          return (textList.empty()) ? Text::baseLine() : 0.0;
          }

    //---------------------------------------------------------
    //   text
    //---------------------------------------------------------

    QString HDegree::text() const
          {
          if (_type == HDegreeType::UNDEF)
                return QString();
          const char* d = 0;
          switch(_type) {
                case HDegreeType::UNDEF: break;
                case HDegreeType::ADD:         d= "add"; break;
                case HDegreeType::ALTER:       d= "alt"; break;
                case HDegreeType::SUBTRACT:    d= "sub"; break;
                }
          QString degree(d);
          switch(_alter) {
                case -1:          degree += "b"; break;
                case 1:           degree += "#"; break;
                default:          break;
                }
          QString s = QString("%1").arg(_value);
          QString ss = degree + s;
          return ss;
          }

    //---------------------------------------------------------
    //   fromXml
    //    lookup harmony in harmony data base
    //    using musicXml "kind" string and degree list
    //---------------------------------------------------------

    const ChordDescription* Harmony::fromXml(const QString& kind, const QList<HDegree>& dl)
          {
          QStringList degrees;

          foreach(const HDegree& d, dl)
                degrees.append(d.text());

          QString lowerCaseKind = kind.toLower();
          const ChordList* cl = score()->style().chordList();
          foreach(const ChordDescription& cd, *cl) {
                QString k     = cd.xmlKind;
                QString lowerCaseK = k.toLower(); // required for xmlKind Tristan
                QStringList d = cd.xmlDegrees;
                if ((lowerCaseKind == lowerCaseK) && (d == degrees)) {
    //                  qDebug("harmony found in db: %s %s -> %d", qPrintable(kind), qPrintable(degrees), cd->id);
                      return &cd;
                      }
                }
          return 0;
          }

    //---------------------------------------------------------
    //   fromXml
    //    lookup harmony in harmony data base
    //    using musicXml "kind" string only
    //---------------------------------------------------------

    const ChordDescription* Harmony::fromXml(const QString& kind)
          {
          QString lowerCaseKind = kind.toLower();
          const ChordList* cl = score()->style().chordList();
          foreach(const ChordDescription& cd, *cl) {
                if (lowerCaseKind == cd.xmlKind)
                      return &cd;
                }
          return 0;
          }

    //---------------------------------------------------------
    //   fromXml
    //    construct harmony directly from XML
    //    build name first
    //    then generate chord description from that
    //---------------------------------------------------------

    const ChordDescription* Harmony::fromXml(const QString& kind, const QString& kindText, const QString& symbols, const QString& parens, const QList<HDegree>& dl)
          {
          ParsedChord* pc = new ParsedChord;
          _textName = pc->fromXml(kind, kindText, symbols, parens, dl, score()->style().chordList());
          _parsedForm = pc;
          const ChordDescription* cd = getDescription(_textName,pc);
          return cd;
          }

    //---------------------------------------------------------
    //   descr
    //    look up id in chord list
    //    return chord description if found, or null
    //---------------------------------------------------------

    const ChordDescription* Harmony::descr() const
          {
          return score()->style().chordDescription(_id);
          }

    //---------------------------------------------------------
    //   descr
    //    look up name in chord list
    //    optionally look up by parsed chord as fallback
    //    return chord description if found, or null
    //---------------------------------------------------------

    const ChordDescription* Harmony::descr(const QString& name, const ParsedChord* pc) const
          {
          const ChordList* cl = score()->style().chordList();
          const ChordDescription* match = 0;
          if (cl) {
                foreach (const ChordDescription& cd, *cl) {
                      foreach (const QString& s, cd.names) {
                            if (s == name)
                                  return &cd;
                            else if (pc) {
                                  foreach (const ParsedChord& sParsed, cd.parsedChords) {
                                        if (sParsed == *pc)
                                              match = &cd;
                                        }
                                  }
                            }
                      }
                }
          // exact match failed, so fall back on parsed match if one was found
          return match;
          }

    //---------------------------------------------------------
    //   getDescription
    //    look up id in chord list
    //    return chord description if found
    //    if not found, and chord is parseable,
    //    generate a new chord description
    //    and add to chord list
    //---------------------------------------------------------

    const ChordDescription* Harmony::getDescription()
          {
          const ChordDescription* cd = descr();
          if (cd && !cd->names.empty())
                _textName = cd->names.front();
          else if (_textName != "") {
                cd = generateDescription();
                _id = cd->id;
                }
          return cd;
          }

    //---------------------------------------------------------
    //   getDescription
    //    same but lookup by name and optionally parsed chord
    //---------------------------------------------------------

    const ChordDescription* Harmony::getDescription(const QString& name, const ParsedChord* pc)
          {
          const ChordDescription* cd = descr(name, pc);
          if (cd)
                _id = cd->id;
          else {
                cd = generateDescription();
                _id = cd->id;
                }
          return cd;
          }

    //---------------------------------------------------------
    //   generateDescription
    //    generate new chord description from _textName
    //    add to chord list using private id
    //---------------------------------------------------------

    const ChordDescription* Harmony::generateDescription()
          {
          ChordList* cl = score()->style().chordList();
          ChordDescription cd(_textName);
          cd.complete(_parsedForm, cl);
          // remove parsed chord from description
          // so we will only match it literally in the future
          cd.parsedChords.clear();
          return &*cl->insert(cd.id, cd);
          }

    //---------------------------------------------------------
    //   textChanged
    //    text may have changed
    //---------------------------------------------------------

    void Harmony::textChanged()
          {
    //      Text::createLayout();
          setHarmony(plainText());
          }

    //---------------------------------------------------------
    //   layout
    //---------------------------------------------------------

    void Harmony::layout()
          {
          calculateBoundingRect();    // for normal symbols this is called in layout: computeMinWidth()

          if (!parent()) {
                setPos(0.0, 0.0);
                return;
                }

          qreal yy = 0.0;
          //qreal _spatium  = spatium();

          if (parent()->type() == ElementType::SEGMENT) {
                Segment* s = static_cast<Segment*>(parent());
                // look for fret diagram
                bool fretsFound = false;
                for (Element* e : s->annotations()) {
                      if (e->type() == ElementType::FRET_DIAGRAM && e->track() == track()) {
                            yy -= score()->styleP(StyleIdx::fretY);
                            e->layout();
                            yy -= e->height();
                            yy -= score()->styleP(StyleIdx::harmonyFretDist);
                            fretsFound = true;
                            break;
                            }
                      }
                if (!fretsFound)
                      yy -= score()->styleP(StyleIdx::harmonyY);
                }
          else if (parent()->type() == ElementType::FRET_DIAGRAM) {
                qDebug("Harmony %s with fret diagram as parent", qPrintable(_textName)); // not possible?
                yy = score()->styleP(StyleIdx::harmonyFretDist);
                }
    //TODO      yy += textStyle().offset(_spatium).y();
    #if 0
          if (!editMode()) {
                qreal hb = lineHeight() - Text::baseLine();
                if (align() & Align::BOTTOM)
                      yy -= hb;
                else if (align() & Align::VCENTER) {
                      yy -= hb;
                      yy += (height() * .5);
                      }
                else if (align() & Align::BASELINE) {
                      }
                else { // Align::TOP
                      yy -= hb;
                      yy += height();
                      }
                }
    #endif

          qreal xx = 0.0; // offset(_spatium).x();
    #if 0
          if (!editMode()) {
                qreal cw = symWidth(SymId::noteheadBlack);
                if (align() & Align::RIGHT) {
                      xx += cw;
                      xx -= width();
                      }
                else if (align() & Align::HCENTER) {
                      xx += (cw * .5);
                      xx -= (width() * .5);
                      }
                }
    #endif
          setPos(xx, yy);

          if (!readPos().isNull()) {
                // version 114 is measure based
                // rebase to segment
                if (score()->mscVersion() == 114) {
                      setReadPos(readPos() - parent()->pos());
                      }
                setUserOff(readPos() - ipos());
                setReadPos(QPointF());
                }

          if (parent()->type() == ElementType::FRET_DIAGRAM && parent()->parent()->type() == ElementType::SEGMENT) {
                qDebug("Harmony %s with fret diagram as parent and segment as grandparent", qPrintable(_textName));
    //            MStaff* mstaff = static_cast<Segment*>(parent()->parent())->measure()->mstaff(staffIdx());
    //WS            qreal dist = -(bbox().top());
    //            mstaff->distanceUp = qMax(mstaff->distanceUp, dist + _spatium);
                }

          if (hasFrame()) {
                QRectF saveBbox = bbox();
                setbbox(bboxtight());
                layoutFrame();
                setbbox(saveBbox);
                }

          }

    //---------------------------------------------------------
    //   calculateBoundingRect
    //---------------------------------------------------------

    void Harmony::calculateBoundingRect()
          {
    //      if (editMode() || textList.empty()) {
          if (textList.empty()) {
                Text::layout1();
                setbboxtight(bbox());
                }
          else {
                // textStyle().layout(this);
                QRectF bb, tbb;
                foreach(const TextSegment* ts, textList) {
                      bb |= ts->boundingRect().translated(ts->x, ts->y);
                      tbb |= ts->tightBoundingRect().translated(ts->x, ts->y);
                      }
                setbbox(bb);
                setbboxtight(tbb);
                }
          }

    //---------------------------------------------------------
    //   draw
    //---------------------------------------------------------

    void Harmony::draw(QPainter* painter) const
          {
          // painter->setPen(curColor());
    //      if (editMode() || textList.empty()) {
          if (textList.empty()) {
                Text::draw(painter);
                return;
                }
    #if 0 //TODO
          if (textStyle().hasFrame()) {
                if (textStyle().frameWidth().val() != 0.0) {
                      QColor color = frameColor();
                      QPen pen(color, textStyle().frameWidth().val() * spatium(), Qt::SolidLine,
                         Qt::SquareCap, Qt::MiterJoin);
                      painter->setPen(pen);
                      }
                else
                      painter->setPen(Qt::NoPen);
                QColor bg(textStyle().backgroundColor());
                painter->setBrush(bg.alpha() ? QBrush(bg) : Qt::NoBrush);
                if (textStyle().circle())
                      painter->drawArc(frame, 0, 5760);
                else {
                      int r2 = textStyle().frameRound();
                      if (r2 > 99)
                            r2 = 99;
                      painter->drawRoundedRect(frame, textStyle().frameRound(), r2);
                      }
                }
    #endif
          painter->setBrush(Qt::NoBrush);
          QColor color = textColor();
          painter->setPen(color);
          foreach(const TextSegment* ts, textList) {
                QFont f(ts->font);
                f.setPointSizeF(f.pointSizeF() * MScore::pixelRatio);
                painter->setFont(f);
                painter->drawText(QPointF(ts->x, ts->y), ts->text);
                }
          }

    //---------------------------------------------------------
    //   TextSegment
    //---------------------------------------------------------

    TextSegment::TextSegment(const QString& s, const QFont& f, qreal x, qreal y)
          {
          set(s, f, x, y);
          select = false;
          }

    //---------------------------------------------------------
    //   width
    //---------------------------------------------------------

    qreal TextSegment::width() const
          {
          QFontMetricsF fm(font, MScore::paintDevice());
    #if 1
          return fm.width(text);
    #else
          qreal w = 0.0;
          foreach(QChar c, text) {
                // if we calculate width by character, at least skip high surrogates
                if (c.isHighSurrogate())
                      continue;
                w += fm.width(c);
                }
          return w;
    #endif
          }

    //---------------------------------------------------------
    //   boundingRect
    //---------------------------------------------------------

    QRectF TextSegment::boundingRect() const
          {
          QFontMetricsF fm(font, MScore::paintDevice());
          return fm.boundingRect(text);
          }

    //---------------------------------------------------------
    //   tightBoundingRect
    //---------------------------------------------------------

    QRectF TextSegment::tightBoundingRect() const
          {
          QFontMetricsF fm(font, MScore::paintDevice());
          return fm.tightBoundingRect(text);
          }

    //---------------------------------------------------------
    //   set
    //---------------------------------------------------------

    void TextSegment::set(const QString& s, const QFont& f, qreal _x, qreal _y)
          {
          font = f;
          x    = _x;
          y    = _y;
          setText(s);
          }

    //---------------------------------------------------------
    //   render
    //---------------------------------------------------------

    void Harmony::render(const QString& s, qreal& x, qreal& y)
          {
          int fontIdx = 0;
          if(!s.isEmpty()) {
                TextSegment* ts = new TextSegment(s, fontList[fontIdx], x, y);
                textList.append(ts);
                x += ts->width();
                }
          }

    //---------------------------------------------------------
    //   render
    //---------------------------------------------------------

    void Harmony::render(const QList<RenderAction>& renderList, qreal& x, qreal& y, int tpc, NoteSpellingType noteSpelling, NoteCaseType noteCase)
          {
          ChordList* chordList = score()->style().chordList();
          QStack<QPointF> stack;
          int fontIdx = 0;
          qreal _spatium = spatium();
          qreal mag = _spatium / SPATIUM20;

          foreach(const RenderAction& a, renderList) {
                if (a.type == RenderAction::RenderActionType::SET) {
                      TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
                      ChordSymbol cs = chordList->symbol(a.text);
                      if (cs.isValid()) {
                            ts->font = fontList[cs.fontIdx];
                            ts->setText(cs.value);
                            }
                      else
                            ts->setText(a.text);
                      textList.append(ts);
                      x += ts->width();
                      }
                else if (a.type == RenderAction::RenderActionType::MOVE) {
                      x += a.movex * mag;
                      y += a.movey * mag;
                      }
                else if (a.type == RenderAction::RenderActionType::PUSH)
                      stack.push(QPointF(x,y));
                else if (a.type == RenderAction::RenderActionType::POP) {
                      if (!stack.empty()) {
                            QPointF pt = stack.pop();
                            x = pt.x();
                            y = pt.y();
                            }
                      else
                            qDebug("RenderAction::RenderActionType::POP: stack empty");
                      }
                else if (a.type == RenderAction::RenderActionType::NOTE) {
                      QString c;
                      int acc;
                      tpc2name(tpc, noteSpelling, noteCase, c, acc);
                      TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
                      QString lookup = "note" + c;
                      ChordSymbol cs = chordList->symbol(lookup);
                      if (!cs.isValid())
                            cs = chordList->symbol(c);
                      if (cs.isValid()) {
                            ts->font = fontList[cs.fontIdx];
                            ts->setText(cs.value);
                            }
                      else
                            ts->setText(c);
                      textList.append(ts);
                      x += ts->width();
                      }
                else if (a.type == RenderAction::RenderActionType::ACCIDENTAL) {
                      QString c;
                      QString acc;
                      QString context = "accidental";
                      tpc2name(tpc, noteSpelling, noteCase, c, acc);
                      // German spelling - use special symbol for accidental in TPC_B_B
                      // to allow it to be rendered as either Bb or B
                      if (tpc == Tpc::TPC_B_B && noteSpelling == NoteSpellingType::GERMAN)
                            context = "german_B";
                      if (acc != "") {
                            TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
                            QString lookup = context + acc;
                            ChordSymbol cs = chordList->symbol(lookup);
                            if (!cs.isValid())
                                  cs = chordList->symbol(acc);
                            if (cs.isValid()) {
                                  ts->font = fontList[cs.fontIdx];
                                  ts->setText(cs.value);
                                  }
                            else
                                  ts->setText(acc);
                            textList.append(ts);
                            x += ts->width();
                            }
                      }
                else
                      qDebug("Harmony::render(): unknown render action %d", static_cast<int>(a.type));
                }
          }

    //---------------------------------------------------------
    //   render
    //    construct Chord Symbol
    //---------------------------------------------------------

    void Harmony::render(const TextStyle* /*st*/)
          {
    #if 0 // TODO
          int capo = score()->styleI(StyleIdx::capoPosition);

          if (st == 0)
                st = &textStyle();
          ChordList* chordList = score()->style().chordList();

          fontList.clear();
          foreach(ChordFont cf, chordList->fonts) {
                if (cf.family.isEmpty() || cf.family == "default")
                      fontList.append(st->font(spatium() * cf.mag));
                else {
                      QFont ff(st->font(spatium() * cf.mag));
                      ff.setFamily(cf.family);
                      fontList.append(ff);
                      }
                }
          if (fontList.empty())
                fontList.append(st->font(spatium()));

          foreach(const TextSegment* s, textList)
                delete s;
          textList.clear();
          qreal x = 0.0, y = 0.0;

          determineRootBaseSpelling();

          if (_leftParen)
                render("( ", x, y);

          if (_rootTpc != Tpc::TPC_INVALID) {
                // render root
                render(chordList->renderListRoot, x, y, _rootTpc, _rootSpelling, _rootRenderCase);
                // render extension
                const ChordDescription* cd = getDescription();
                if (cd)
                      render(cd->renderList, x, y, 0);
                }
          else
                render(_textName, x, y);

          // render bass
          if (_baseTpc != Tpc::TPC_INVALID)
                render(chordList->renderListBase, x, y, _baseTpc, _baseSpelling, _baseRenderCase);

          if (_rootTpc != Tpc::TPC_INVALID && capo > 0 && capo < 12) {
                int tpcOffset[] = { 0, 5, -2, 3, -4, 1, 6, -1, 4, -3, 2, -5 };
                int capoRootTpc = _rootTpc + tpcOffset[capo];
                int capoBassTpc = _baseTpc;

                if (capoBassTpc != Tpc::TPC_INVALID)
                      capoBassTpc += tpcOffset[capo];

                /*
                 * For guitarists, avoid x and bb in Root or Bass,
                 * and also avoid E#, B#, Cb and Fb in Root.
                 */
                if (capoRootTpc < 8 || (capoBassTpc != Tpc::TPC_INVALID && capoBassTpc < 6)) {
                      capoRootTpc += 12;
                      if (capoBassTpc != Tpc::TPC_INVALID)
                            capoBassTpc += 12;
                      }
                else if (capoRootTpc > 24 || (capoBassTpc != Tpc::TPC_INVALID && capoBassTpc > 26)) {
                      capoRootTpc -= 12;
                      if (capoBassTpc != Tpc::TPC_INVALID)
                            capoBassTpc -= 12;
                      }

                render("(", x, y);
                render(chordList->renderListRoot, x, y, capoRootTpc, _rootSpelling, _rootRenderCase);

                // render extension
                const ChordDescription* cd = getDescription();
                if (cd)
                      render(cd->renderList, x, y, 0);

                if (capoBassTpc != Tpc::TPC_INVALID)
                      render(chordList->renderListBase, x, y, capoBassTpc, _baseSpelling, _baseRenderCase);
                render(")", x, y);
                }

          if (_rightParen)
                render(" )", x, y);
    #endif
          }

    //---------------------------------------------------------
    //   spatiumChanged
    //---------------------------------------------------------

    void Harmony::spatiumChanged(qreal oldValue, qreal newValue)
          {
          Text::spatiumChanged(oldValue, newValue);
          render();
          }

    //---------------------------------------------------------
    //   localSpatiumChanged
    //---------------------------------------------------------

    void Harmony::localSpatiumChanged(qreal oldValue, qreal newValue)
          {
          Text::localSpatiumChanged(oldValue, newValue);
          render();
          }

    //---------------------------------------------------------
    //   extensionName
    //---------------------------------------------------------

    const QString& Harmony::extensionName() const
          {
          return _textName;
          }

    //---------------------------------------------------------
    //   xmlKind
    //---------------------------------------------------------

    QString Harmony::xmlKind() const
          {
          const ChordDescription* cd = descr();
          return cd ? cd->xmlKind : QString();
          }

    //---------------------------------------------------------
    //   xmlText
    //---------------------------------------------------------

    QString Harmony::xmlText() const
          {
          const ChordDescription* cd = descr();
          return cd ? cd->xmlText : QString();
          }

    //---------------------------------------------------------
    //   xmlSymbols
    //---------------------------------------------------------

    QString Harmony::xmlSymbols() const
          {
          const ChordDescription* cd = descr();
          return cd ? cd->xmlSymbols : QString();
          }

    //---------------------------------------------------------
    //   xmlParens
    //---------------------------------------------------------

    QString Harmony::xmlParens() const
          {
          const ChordDescription* cd = descr();
          return cd ? cd->xmlParens : QString();
          }

    //---------------------------------------------------------
    //   xmlDegrees
    //---------------------------------------------------------

    QStringList Harmony::xmlDegrees() const
          {
          const ChordDescription* cd = descr();
          return cd ? cd->xmlDegrees : QStringList();
          }

    //---------------------------------------------------------
    //   degree
    //---------------------------------------------------------

    HDegree Harmony::degree(int i) const
          {
          return _degreeList.value(i);
          }

    //---------------------------------------------------------
    //   addDegree
    //---------------------------------------------------------

    void Harmony::addDegree(const HDegree& d)
          {
          _degreeList << d;
          }

    //---------------------------------------------------------
    //   numberOfDegrees
    //---------------------------------------------------------

    int Harmony::numberOfDegrees() const
          {
          return _degreeList.size();
          }

    //---------------------------------------------------------
    //   clearDegrees
    //---------------------------------------------------------

    void Harmony::clearDegrees()
          {
          _degreeList.clear();
          }

    //---------------------------------------------------------
    //   degreeList
    //---------------------------------------------------------

    const QList<HDegree>& Harmony::degreeList() const
          {
          return _degreeList;
          }

    //---------------------------------------------------------
    //   parsedForm
    //---------------------------------------------------------

    const ParsedChord* Harmony::parsedForm()
          {
          if (!_parsedForm) {
                ChordList* cl = score()->style().chordList();
                _parsedForm = new ParsedChord();
                _parsedForm->parse(_textName, cl, false);
                }
          return _parsedForm;
          }

    //---------------------------------------------------------
    //   accessibleInfo
    //---------------------------------------------------------

    QString Harmony::accessibleInfo() const
          {
          return QString("%1: %2").arg(Element::accessibleInfo()).arg(harmonyName());
          }

    //---------------------------------------------------------
    //   screenReaderInfo
    //---------------------------------------------------------

    QString Harmony::screenReaderInfo() const
          {
          QString rez = Element::accessibleInfo();
          if (_rootTpc != Tpc::TPC_INVALID)
                rez = QString("%1 %2").arg(rez).arg(tpc2name(_rootTpc, NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));

          if (const_cast<Harmony*>(this)->parsedForm() && !hTextName().isEmpty()) {
                QString aux = const_cast<Harmony*>(this)->parsedForm()->handle();
                aux = aux.replace("#", QObject::tr("sharp")).replace("<", "");
                QString extension = "";

                foreach (QString s, aux.split(">", QString::SkipEmptyParts)) {
                      if(!s.contains("blues"))
                            s.replace("b", QObject::tr("flat"));
                      extension += s + " ";
                      }
                rez = QString("%1 %2").arg(rez).arg(extension);
                }
          else {
                rez = QString("%1 %2").arg(rez).arg(hTextName());
                }

          if (_baseTpc != Tpc::TPC_INVALID)
                rez = QString("%1 / %2").arg(rez).arg(tpc2name(_baseTpc, NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));

          return rez;
          }

    //---------------------------------------------------------
    //   acceptDrop
    //---------------------------------------------------------

    bool Harmony::acceptDrop(EditData& data) const
          {
          return data.element->type() == ElementType::FRET_DIAGRAM;
          }

    //---------------------------------------------------------
    //   drop
    //---------------------------------------------------------

    Element* Harmony::drop(EditData& data)
          {
          Element* e = data.element;
          if (e->type() == ElementType::FRET_DIAGRAM) {
                FretDiagram* fd = toFretDiagram(e);
                fd->setParent(parent());
                fd->setTrack(track());
                score()->undoAddElement(fd);
                }
          else {
                qWarning("Harmony: cannot drop <%s>\n", e->name());
                delete e;
                e = 0;
                }
          return e;
          }

    //---------------------------------------------------------
    //   propertyDefault
    //---------------------------------------------------------

    QVariant Harmony::propertyDefault(P_ID id) const
          {
          switch (id) {
                case P_ID::SUB_STYLE:
                      return int(SubStyle::HARMONY);
                default:
                      return Text::propertyDefault(id);
                }
          }

    }

↑ V547 Expression 'cl' is always true.

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: _rootSpelling, _baseSpelling, _rootRenderCase, _baseRenderCase.

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: _rootSpelling, _baseSpelling, _rootRenderCase, _baseRenderCase.