code: //=============================================================================
  //  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);
        else if (_harmonyType != HarmonyType::STANDARD)
              r = _function;
  
        if (_textName != "") {
              e = _textName;
              if (_harmonyType != HarmonyType::ROMAN)
                    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();
        if (_baseTpc == Tpc::TPC_INVALID)
              return rootName();
        return tpc2name(_baseTpc, _baseSpelling, _baseCase);
        }
  
  
  bool Harmony::isRealizable() const
        {
        return (_rootTpc != Tpc::TPC_INVALID)
                    || (_harmonyType == HarmonyType::NASHVILLE); // unable to fully check at for nashville at the moment
        }
  
  //---------------------------------------------------------
  //   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();
        for (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");
        }
  
  //---------------------------------------------------------
  //   chordSymbolStyle
  //---------------------------------------------------------
  
  const ElementStyle chordSymbolStyle {
        { Sid::harmonyPlacement, Pid::PLACEMENT  },
        { Sid::minHarmonyDistance, Pid::MIN_DISTANCE },
        { Sid::harmonyPlay, Pid::PLAY },
        { Sid::harmonyVoiceLiteral, Pid::HARMONY_VOICE_LITERAL },
        { Sid::harmonyVoicing, Pid::HARMONY_VOICING },
        { Sid::harmonyDuration, Pid::HARMONY_DURATION }
        };
  
  //---------------------------------------------------------
  //   Harmony
  //---------------------------------------------------------
  
  Harmony::Harmony(Score* s)
     : TextBase(s, Tid::HARMONY_A, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
        {
        _rootTpc    = Tpc::TPC_INVALID;
        _baseTpc    = Tpc::TPC_INVALID;
        _rootSpelling = NoteSpellingType::STANDARD;
        _baseSpelling = NoteSpellingType::STANDARD;
        _rootCase   = NoteCaseType::CAPITAL;
        _baseCase   = NoteCaseType::CAPITAL;
        _rootRenderCase = NoteCaseType::CAPITAL;
        _baseRenderCase = NoteCaseType::CAPITAL;
        _id         = -1;
        _parsedForm = 0;
        _harmonyType = HarmonyType::STANDARD;
        _leftParen  = false;
        _rightParen = false;
        _realizedHarmony = RealizedHarmony(this);
        initElementStyle(&chordSymbolStyle);
        }
  
  Harmony::Harmony(const Harmony& h)
     : TextBase(h)
        {
        _rootTpc    = h._rootTpc;
        _baseTpc    = h._baseTpc;
        _rootSpelling = h._rootSpelling;
        _baseSpelling = h._baseSpelling;
        _rootCase   = h._rootCase;
        _baseCase   = h._baseCase;
        _rootRenderCase = h._rootRenderCase;
        _baseRenderCase = h._baseRenderCase;
        _id         = h._id;
        _leftParen  = h._leftParen;
        _rightParen = h._rightParen;
        _degreeList = h._degreeList;
        _parsedForm = h._parsedForm ? new ParsedChord(*h._parsedForm) : 0;
        _harmonyType = h._harmonyType;
        _textName   = h._textName;
        _userName   = h._userName;
        _function   = h._function;
        _play       = h._play;
        _realizedHarmony = h._realizedHarmony;
        _realizedHarmony.setHarmony(this);
        for (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()
        {
        for (const TextSegment* ts : textList)
              delete ts;
        if (_parsedForm)
              delete _parsedForm;
        }
  
  //---------------------------------------------------------
  //   write
  //---------------------------------------------------------
  
  void Harmony::write(XmlWriter& xml) const
        {
        if (!xml.canWrite(this))
              return;
        xml.stag(this);
        writeProperty(xml, Pid::HARMONY_TYPE);
        if (_leftParen)
              xml.tagE("leftParen");
        if (_rootTpc != Tpc::TPC_INVALID || _baseTpc != Tpc::TPC_INVALID) {
              int rRootTpc = _rootTpc;
              int rBaseTpc = _baseTpc;
              if (staff()) {
                    // parent can be a fret diagram
                    Segment* segment = parent()->isSegment() ? toSegment(parent()) : toSegment(parent()->parent());
                    Fraction tick = segment ? segment->tick() : Fraction(-1,1);
                    const Interval& interval = part()->instrument(tick)->transpose();
                    if (xml.clipboardmode() && !score()->styleB(Sid::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.isEmpty())
                    xml.tag("name", writeName);
  
              if (rBaseTpc != Tpc::TPC_INVALID) {
                    xml.tag("base", rBaseTpc);
                    if (_baseCase != NoteCaseType::CAPITAL)
                          xml.tag("baseCase", static_cast<int>(_baseCase));
                    }
              for (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);
        if (!_function.isEmpty())
              xml.tag("function", _function);
        TextBase::writeProperties(xml, false, true);
        //Pid::PLAY, Pid::HARMONY_VOICE_LITERAL, Pid::HARMONY_VOICING, Pid::HARMONY_DURATION
        //written by the above function call because they are part of element style
        if (_rightParen)
              xml.tagE("rightParen");
        xml.etag();
        }
  
  //---------------------------------------------------------
  //   read
  //---------------------------------------------------------
  
  void Harmony::read(XmlReader& e)
        {
        while (e.readNextStartElement()) {
              const QStringRef& tag(e.name());
              if (tag == "base")
                    setBaseTpc(e.readInt());
              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")
                    setRootTpc(e.readInt());
              else if (tag == "rootCase")
                    _rootCase = static_cast<NoteCaseType>(e.readInt());
              else if (tag == "function")
                    _function = e.readElementText();
              else if (tag == "degree") {
                    int degreeValue = 0;
                    int degreeAlter = 0;
                    QString degreeType = "";
                    while (e.readNextStartElement()) {
                          const QStringRef& t(e.name());
                          if (t == "degree-value")
                                degreeValue = e.readInt();
                          else if (t == "degree-alter")
                                degreeAlter = e.readInt();
                          else if (t == "degree-type")
                                degreeType = e.readElementText();
                          else
                                e.unknown();
                          }
                    if (degreeValue <= 0 || degreeValue > 13
                        || degreeAlter < -2 || degreeAlter > 2
                        || (degreeType != "add" && degreeType != "alter" && degreeType != "subtract")) {
                          qDebug("incorrect degree: degreeValue=%d degreeAlter=%d degreeType=%s",
                                 degreeValue, degreeAlter, qPrintable(degreeType));
                          }
                    else {
                          if (degreeType == "add")
                                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 (readProperty(tag, e, Pid::POS_ABOVE))
                    ;
              else if (readProperty(tag, e, Pid::HARMONY_TYPE))
                    ;
              else if (readProperty(tag, e, Pid::PLAY))
                    ;
              else if (readProperty(tag, e, Pid::HARMONY_VOICE_LITERAL))
                    ;
              else if (readProperty(tag, e, Pid::HARMONY_VOICING))
                    ;
              else if (readProperty(tag, e, Pid::HARMONY_DURATION))
                    ;
              else if (!TextBase::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();
        setPlainText(harmonyName());
        }
  
  //---------------------------------------------------------
  //   determineRootBaseSpelling
  //---------------------------------------------------------
  
  void Harmony::determineRootBaseSpelling(NoteSpellingType& rootSpelling, NoteCaseType& rootCase,
     NoteSpellingType& baseSpelling, NoteCaseType& baseCase)
        {
        // spelling
        if (score()->styleB(Sid::useStandardNoteNames))
              rootSpelling = NoteSpellingType::STANDARD;
        else if (score()->styleB(Sid::useGermanNoteNames))
              rootSpelling = NoteSpellingType::GERMAN;
        else if (score()->styleB(Sid::useFullGermanNoteNames))
              rootSpelling = NoteSpellingType::GERMAN_PURE;
        else if (score()->styleB(Sid::useSolfeggioNoteNames))
              rootSpelling = NoteSpellingType::SOLFEGGIO;
        else if (score()->styleB(Sid::useFrenchNoteNames))
              rootSpelling = NoteSpellingType::FRENCH;
        baseSpelling = rootSpelling;
  
        // case
  
        // always use case as typed if automatic capitalization is off
        if (!score()->styleB(Sid::automaticCapitalization)) {
              rootCase = _rootCase;
              baseCase = _baseCase;
              return;
              }
  
        // set default
        if (score()->styleB(Sid::allCapsNoteNames)) {
              rootCase = NoteCaseType::UPPER;
              baseCase = NoteCaseType::UPPER;
              }
        else {
              rootCase = NoteCaseType::CAPITAL;
              baseCase = NoteCaseType::CAPITAL;
              }
  
        // override for bass note
        if (score()->styleB(Sid::lowerCaseBassNotes))
              baseCase = NoteCaseType::LOWER;
  
        // override for minor chords
        if (score()->styleB(Sid::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.clear();
        bool useLiteral = false;
        if (ss.endsWith(' '))
              useLiteral = true;
  
        if (_harmonyType == HarmonyType::ROMAN) {
              _userName = ss;
              _textName = ss;
              *root = Tpc::TPC_INVALID;
              *base = Tpc::TPC_INVALID;
              return 0;
              }
  
        // pre-process for parentheses
        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
        if (s.isEmpty())
              return 0;
  
        // pre-process for lower case minor chords
        bool preferMinor;
        if (score()->styleB(Sid::lowerCaseMinorChords) && s[0].isLower())
              preferMinor = true;
        else
              preferMinor = false;
  
        if (_harmonyType == HarmonyType::NASHVILLE) {
              int n = 0;
              if (s[0].isDigit())
                    n = 1;
              else if (s[1].isDigit())
                    n = 2;
              _function = s.mid(0, n);
              s = s.mid(n);
              *root = Tpc::TPC_INVALID;
              *base = Tpc::TPC_INVALID;
              }
        else {
              determineRootBaseSpelling();
              int idx;
              int r = convertNote(s, _rootSpelling, _rootCase, idx);
              if (r == Tpc::TPC_INVALID) {
                    if (s[0] == '/')
                          idx = 0;
                    else {
                          qDebug("failed <%s>", qPrintable(ss));
                          _userName = s;
                          _textName = s;
                          return 0;
                          }
                    }
              *root = r;
              *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()) {
              // convert chord symbol to plain text
              setPlainText(harmonyName());
              // clear rendering
              for (const TextSegment* t : textList)
                    delete t;
              textList.clear();
              }
  
        // layout as text, without position reset
        TextBase::layout1();
        triggerLayout();
  
        TextBase::startEdit(ed);
        }
  
  //---------------------------------------------------------
  //   edit
  //---------------------------------------------------------
  
  bool Harmony::edit(EditData& ed)
        {
        if (ed.key == Qt::Key_Return)
              return true; // Harmony only single line
  
        bool rv = TextBase::edit(ed);
  
        // layout as text, without position reset
        TextBase::layout1();
        triggerLayout();
  
        // check spelling
        int root = TPC_INVALID;
        int base = TPC_INVALID;
        QString str = xmlText();
        showSpell = !str.isEmpty() && !parseHarmony(str, &root, &base, true) && root == TPC_INVALID && _harmonyType == HarmonyType::STANDARD;
        if (showSpell)
              qDebug("bad spell");
  
        return rv;
        }
  
  //---------------------------------------------------------
  //   endEdit
  //---------------------------------------------------------
  
  void Harmony::endEdit(EditData& ed)
        {
        // complete editing: generate xml text, set Pid::TEXT, perform initial layout
        // if text has changed, this also triggers setHarmony() which renders chord symbol
        // but any rendering or layout performed here is tentative,
        // we may still need to substitute special characters,
        // and that cannot be until after editing is completed
        TextBase::endEdit(ed);
  
        // get plain text
        QString s = plainText();
  
        // if user explicitly added symbols to the text,
        // convert them back to their respective replacement texts
        if (harmonyType() != HarmonyType::ROMAN) {
              s.replace("\u1d12b", "bb"); // double-flat
              s.replace("\u266d",  "b");  // flat
              s.replace("\ue260",  "b");  // flat
              // do not replace natural sign
              // (right now adding the symbol explicitly is the only way to force a natural sign to appear at all)
              //s.replace("\u266e",  "n");  // natural, if one day we support that too
              //s.replace("\ue261",  "n");  // natural, if one day we support that too
              s.replace("\u266f",  "#");  // sharp
              s.replace("\ue262",  "#");  // sharp
              s.replace("\u1d12a", "x");  // double-sharp
              s.replace("\u0394",  "^");  // &Delta;
              s.replace("\u00d0",  "o");  // &deg;
              s.replace("\u00f8",  "0");  // &oslash;
              s.replace("\u00d8",  "0");  // &Oslash;
              }
        else {
              s.replace("\ue260",  "\u266d");     // flat
              s.replace("\ue261",  "\u266e");     // natural
              s.replace("\ue262",  "\u266f");     // sharp
              }
  
        //play chord on edit and set dirty
        score()->setPlayChord(true);
        _realizedHarmony.setDirty(true);
  
        // render and layout chord symbol
        // (needs to be done here if text hasn't changed, or redone if replacemens were performed above)
        score()->startCmd();
        setHarmony(s);
        layout1();
        triggerLayout();
        score()->endCmd();
  
        // disable spell check
        showSpell = false;
  
        if (links()) {
              for (ScoreElement* e : *links()) {
                    if (e == this)
                          continue;
                    Harmony* h = toHarmony(e);
                    // transpose if necessary
                    // at this point chord will already have been rendered in same key as original
                    // (as a result of TextBase::endEdit() calling setText() for linked elements)
                    // we may now need to change the TPC's and the text, and re-render
                    if (score()->styleB(Sid::concertPitch) != h->score()->styleB(Sid::concertPitch)) {
                          Part* partDest = h->part();
                          Segment* segment = toSegment(parent());
                          Fraction tick = segment ? segment->tick() : Fraction(-1,1);
                          Interval interval = partDest->instrument(tick)->transpose();
                          if (!interval.isZero()) {
                                if (!h->score()->styleB(Sid::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);
                                h->setPlainText(h->harmonyName());
                                h->setHarmony(h->plainText());
                                h->triggerLayout();
                                }
                          }
                    }
              }
        }
  
  //---------------------------------------------------------
  //   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
              for (const TextSegment* ts : textList)
                    delete ts;
              textList.clear();
              setRootTpc(Tpc::TPC_INVALID);
              setBaseTpc(Tpc::TPC_INVALID);
              _id = -1;
              render();
              }
        }
  
  //---------------------------------------------------------
  //   baseLine
  //---------------------------------------------------------
  
  qreal Harmony::baseLine() const
        {
        return (textList.empty()) ? TextBase::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;
        }
  
  //---------------------------------------------------------
  //   findNext
  ///   find the next Harmony in the score
  ///
  ///   returns 0 if there is none
  //---------------------------------------------------------
  Harmony* Harmony::findNext() const
        {
        Segment* seg = toSegment(parent());
        Segment* cur = seg->next1();
        while (cur) {
              //find harmony on same track
              Element* e = cur->findAnnotation(ElementType::HARMONY,
                                         track(), track());
              if (e) {
                    //we have found harmony element
                    return toHarmony(e);
                    }
              cur = cur->next1();
              }
        return 0;
        }
  
  //---------------------------------------------------------
  //   findPrev
  ///   find the previous Harmony in the score
  ///
  ///   returns 0 if there is none
  //---------------------------------------------------------
  Harmony* Harmony::findPrev() const
        {
        Segment* seg = toSegment(parent());
        Segment* cur = seg->prev1();
        while (cur) {
              //find harmony on same track
              Element* e = cur->findAnnotation(ElementType::HARMONY,
                                         track(), track());
              if (e) {
                    //we have found harmony element
                    return toHarmony(e);
                    }
              cur = cur->prev1();
              }
        return 0;
        }
  
  //---------------------------------------------------------
  //   ticksTilNext
  ///   finds ticks until the next chord symbol or end of score
  ///
  ///   stopAtMeasureEnd being set to true will have the loop
  ///   stop at measure end.
  //---------------------------------------------------------
  Fraction Harmony::ticksTilNext(bool stopAtMeasureEnd) const
        {
        Segment* seg = toSegment(parent());
        Fraction duration = seg->ticks();
        Segment* cur = seg->next1();
        while (cur) {
              if (stopAtMeasureEnd && (cur->measure() != seg->measure()))
                    break; //limit by measure end
  
              //find harmony on same track
              Element* e = cur->findAnnotation(ElementType::HARMONY,
                                         track(), track());
              if (e) {
                    //we have found the next chord symbol
                    //set duration to the difference between
                    //the two chord symbols
                    duration = e->tick() - tick();
                    break;
                    }
              //keep adding the duration of the current segment
              //in case we are not able to find a next
              //chord symbol
              duration += cur->ticks();
              cur = cur->next1();
              }
        return duration;
        }
  
  //---------------------------------------------------------
  //   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;
  
        for (const HDegree& d : dl)
              degrees.append(d.text());
  
        QString lowerCaseKind = kind.toLower();
        const ChordList* cl = score()->style().chordList();
        for (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();
        for (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) {
              for (const ChordDescription& cd : *cl) {
                    for (const QString& s : cd.names) {
                          if (s == name)
                                return &cd;
                          else if (pc) {
                                for (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;
        }
  
  //---------------------------------------------------------
  //   getRealizedHarmony
  //    get realized harmony or create one for the current symbol
  //    also updates the realized harmony and accounts for
  //    transposition. RealizedHarmony objects cannot be cached
  //    since the notes generated depends on context rather than
  //    just root, bass, chord symbol, and voicing.
  //---------------------------------------------------------
  
  const RealizedHarmony& Harmony::getRealizedHarmony()
        {
        int offset = 0; //semitone offset for pitch adjustment
        Staff* st = staff();
        Interval interval = st->part()->instrument(tick())->transpose();
        if (!score()->styleB(Sid::concertPitch))
              offset = interval.chromatic;
  
        //Adjust for Nashville Notation, might be temporary
        if (_harmonyType == HarmonyType::NASHVILLE && !_realizedHarmony.valid()) {
              Key key = staff()->key(tick());
              //parse root
              int rootTpc = function2Tpc(_function, key);
  
              //parse bass
              int slash = _textName.lastIndexOf('/');
              int bassTpc;
              if (slash == -1)
                    bassTpc = Tpc::TPC_INVALID;
              else
                    bassTpc = function2Tpc(_textName.mid(slash + 1), key);
              _realizedHarmony.update(rootTpc, bassTpc, offset);
              }
        else
              _realizedHarmony.update(_rootTpc, _baseTpc, offset);
        return _realizedHarmony;
        }
  
  //---------------------------------------------------------
  //   realizedHarmony
  //    get realized harmony or create one for the current symbol
  //    without updating the realized harmony
  //---------------------------------------------------------
  
  RealizedHarmony& Harmony::realizedHarmony()
        {
        return _realizedHarmony;
        }
  
  //---------------------------------------------------------
  //   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);
        }
  
  //---------------------------------------------------------
  //   layout
  //---------------------------------------------------------
  
  void Harmony::layout()
        {
        if (!parent()) {
              setPos(0.0, 0.0);
              setOffset(0.0, 0.0);
              layout1();
              return;
              }
        //if (isStyled(Pid::OFFSET))
        //      setOffset(propertyDefault(Pid::OFFSET).toPointF());
  
        if (placeBelow())
              rypos() = staff() ? staff()->height() : 0.0;
        else
              rypos() = 0.0;
        layout1();
  
        qreal yy = ipos().y();
        qreal xx = 0.0;
  
        if (parent()->isFretDiagram()) {
              if (isStyled(Pid::ALIGN))
                    setAlign(Align::HCENTER | Align::BASELINE);
              yy = -score()->styleP(Sid::harmonyFretDist);
              }
  
        qreal hb = lineHeight() - TextBase::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();
              }
  
        qreal cw = symWidth(SymId::noteheadBlack);
        if (align() & Align::RIGHT) {
              xx += cw;
              xx -= width();
              }
        else if (align() & Align::HCENTER) {
              if (parent()->isFretDiagram()) {
                    FretDiagram* fd = toFretDiagram(parent());
                    xx += fd->centerX();
                    xx -= width() * .5;
                    }
              else {
                    xx += (cw * .5);
                    xx -= (width() * .5);
                    }
              }
  
        setPos(xx, yy);
        }
  
  //---------------------------------------------------------
  //   layout1
  //---------------------------------------------------------
  
  void Harmony::layout1()
        {
        if (isLayoutInvalid())
              createLayout();
        if (textBlockList().empty())
              textBlockList().append(TextBlock());
        calculateBoundingRect();    // for normal symbols this is called in layout: computeMinWidth()
        if (hasFrame())
              layoutFrame();
        score()->addRefresh(canvasBoundingRect());
        }
  
  //---------------------------------------------------------
  //   calculateBoundingRect
  //---------------------------------------------------------
  
  void Harmony::calculateBoundingRect()
        {
        if (textList.empty())
              TextBase::layout1();
        else {
              QRectF bb;
              for (const TextSegment* ts : textList)
                    bb |= ts->tightBoundingRect().translated(ts->x, ts->y);
              setbbox(bb);
              for (int i = 0; i < rows(); ++i) {
                    TextBlock& t = textBlockList()[i];
  
                    // when MS switch to editing Harmony MS draws text defined by textBlockList().
                    // When MS switches back to normal state it draws text from textList
                    // To correct placement of text in editing we need to layout textBlockList() elements
                    t.layout(this);
                    for (auto& s : t.fragments()) {
                          s.pos = { 0, 0 };
                          }
                    }
              }
        }
  
  //---------------------------------------------------------
  //   draw
  //---------------------------------------------------------
  
  void Harmony::draw(QPainter* painter) const
        {
        // painter->setPen(curColor());
        if (textList.empty()) {
              TextBase::draw(painter);
              return;
              }
        if (hasFrame()) {
              if (frameWidth().val() != 0.0) {
                    QColor color = frameColor();
                    QPen pen(color, frameWidth().val() * spatium(), Qt::SolidLine,
                       Qt::SquareCap, Qt::MiterJoin);
                    painter->setPen(pen);
                    }
              else
                    painter->setPen(Qt::NoPen);
              QColor bg(bgColor());
              painter->setBrush(bg.alpha() ? QBrush(bg) : Qt::NoBrush);
              if (circle())
                    painter->drawArc(frame, 0, 5760);
              else {
                    int r2 = frameRound();
                    if (r2 > 99)
                          r2 = 99;
                    painter->drawRoundedRect(frame, frameRound(), r2);
                    }
              }
        painter->setBrush(Qt::NoBrush);
        QColor color = textColor();
        painter->setPen(color);
        for (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);
              }
        }
  
  //---------------------------------------------------------
  //   drawEditMode
  //---------------------------------------------------------
  
  void Harmony::drawEditMode(QPainter* p, EditData& ed)
        {
        TextBase::drawEditMode(p, ed);
  
        QColor originalColor = color();
        if (showSpell) {
              setColor(QColor(Qt::red));
              setSelected(false);
              }
        QPointF pos(canvasPos());
        p->translate(pos);
        TextBase::draw(p);
        p->translate(-pos);
        if (showSpell) {
              setColor(originalColor);
              setSelected(true);
              }
        }
  
  //---------------------------------------------------------
  //   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()) {
              QFont f = _harmonyType != HarmonyType::ROMAN ? fontList[fontIdx] : font();
              TextSegment* ts = new TextSegment(s, f, 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      = magS();
  
  // qDebug("===");
        for (const RenderAction& a : renderList) {
  // a.print();
              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);
                    if (_harmonyType == HarmonyType::NASHVILLE) {
                          qreal nmag = chordList->nominalMag();
                          ts->font.setPointSizeF(ts->font.pointSizeF() * nmag);
                          }
                    textList.append(ts);
                    x += ts->width();
                    }
              else if (a.type == RenderAction::RenderActionType::MOVE) {
                    x += a.movex * mag * _spatium * .2;
                    y += a.movey * mag * _spatium * .2;
                    }
              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;
                    if (tpcIsValid(tpc))
                          tpc2name(tpc, noteSpelling, noteCase, c, acc);
                    else if (_function.size() > 0)
                          c = _function.at(_function.size() - 1);
                    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";
                    if (tpcIsValid(tpc))
                          tpc2name(tpc, noteSpelling, noteCase, c, acc);
                    else if (_function.size() > 1)
                          acc = _function.at(0);
                    // 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("unknown render action %d", static_cast<int>(a.type));
              }
        }
  
  //---------------------------------------------------------
  //   render
  //    construct Chord Symbol
  //---------------------------------------------------------
  
  void Harmony::render()
        {
        int capo = score()->styleI(Sid::capoPosition);
  
        ChordList* chordList = score()->style().chordList();
  
        fontList.clear();
        for (const ChordFont& cf : chordList->fonts) {
              QFont ff(font());
              ff.setPointSizeF(ff.pointSizeF() * cf.mag);
              if (!(cf.family.isEmpty() || cf.family == "default"))
                    ff.setFamily(cf.family);
              fontList.append(ff);
              }
        if (fontList.empty())
              fontList.append(font());
  
        for (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 if (_harmonyType == HarmonyType::NASHVILLE) {
              // render function
              render(chordList->renderListFunction, x, y, _rootTpc, _rootSpelling, _rootRenderCase);
              qreal adjust = chordList->nominalAdjust();
              y += adjust * magS() * spatium() * .2;
              // 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);
        }
  
  //---------------------------------------------------------
  //   spatiumChanged
  //---------------------------------------------------------
  
  void Harmony::spatiumChanged(qreal oldValue, qreal newValue)
        {
        TextBase::spatiumChanged(oldValue, newValue);
        render();
        }
  
  //---------------------------------------------------------
  //   localSpatiumChanged
  //---------------------------------------------------------
  
  void Harmony::localSpatiumChanged(qreal oldValue, qreal newValue)
        {
        TextBase::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();
        }
  
  //---------------------------------------------------------
  //   musicXmlText
  //---------------------------------------------------------
  
  QString Harmony::musicXmlText() 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;
        }
  
  //---------------------------------------------------------
  //   setHarmonyType
  //---------------------------------------------------------
  
  void Harmony::setHarmonyType(HarmonyType val)
        {
        _harmonyType = val;
        setPlacement(Placement(propertyDefault(Pid::PLACEMENT).toInt()));
        switch (_harmonyType) {
              case HarmonyType::STANDARD:
                    initTid(Tid::HARMONY_A);
                    break;
              case HarmonyType::ROMAN:
                    initTid(Tid::HARMONY_ROMAN);
                    break;
              case HarmonyType::NASHVILLE:
                    initTid(Tid::HARMONY_NASHVILLE);
                    break;
              }
        // TODO: convert text
        }
  
  //---------------------------------------------------------
  //   userName
  //---------------------------------------------------------
  
  QString Harmony::userName() const
        {
        switch (_harmonyType) {
              case HarmonyType::ROMAN:
                    return QObject::tr("Roman numeral");
              case HarmonyType::NASHVILLE:
                    return QObject::tr("Nashville number");
              case HarmonyType::STANDARD:
                    break;
              }
        return Element::userName();
        }
  
  //---------------------------------------------------------
  //   accessibleInfo
  //---------------------------------------------------------
  
  QString Harmony::accessibleInfo() const
        {
        return QString("%1: %2").arg(userName()).arg(harmonyName());
        }
  
  //---------------------------------------------------------
  //   screenReaderInfo
  //---------------------------------------------------------
  
  QString Harmony::screenReaderInfo() const
        {
        QString rez = userName();
  
        switch (_harmonyType) {
              case HarmonyType::ROMAN: {
                    QString aux = _textName;
                    bool hasUpper = aux.contains('I') || aux.contains('V');
                    bool hasLower = aux.contains('i') || aux.contains('v');
                    if (hasLower && !hasUpper)
                          rez = QString("%1 %2").arg(rez).arg(QObject::tr("lower case"));
                    aux = aux.toLower();
                    static std::vector<std::pair<QString, QString>> rnaReplacements {
                          { "vii", "7" },
                          { "vi", "6" },
                          { "iv", "4" },
                          { "v", "5" },
                          { "iii", "3" },
                          { "ii", "2" },
                          { "i", "1" },
                          };
                    static std::vector<std::pair<QString, QString>> symbolReplacements {
                          { "b", "♭" },
                          { "h", "♮" },
                          { "#", "♯" },
                          { "bb", "𝄫" },
                          { "##", "𝄪" },
                          // TODO: use SMuFL glyphs and translate
                          //{ "o", ""},
                          //{ "0", ""},
                          //{ "\+", ""},
                          //{ "\^", ""},
                          };
                    for (auto const &r : rnaReplacements)
                          aux.replace(r.first, r.second);
                    for (auto const &r : symbolReplacements) {
                          // only replace when not preceded by backslash
                          QString s = "(?<!\\\\)" + r.first;
                          QRegularExpression re(s);
                          aux.replace(re, r.second);
                          }
                    // construct string one character at a time
                    for (auto c : aux)
                          rez = QString("%1 %2").arg(rez).arg(c);
                    }
                    return rez;
              case HarmonyType::NASHVILLE:
                    if (!_function.isEmpty())
                          rez = QString("%1 %2").arg(rez).arg(_function);
                    break;
              case HarmonyType::STANDARD:
              default:
                    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("♯")).replace("<", "");
              QString extension = "";
  
              for (QString s : aux.split(">", QString::SkipEmptyParts)) {
                    if (!s.contains("blues"))
                          s.replace("b", QObject::tr("♭"));
                    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
        {
        Element* e = data.dropElement;
        if (e->isFretDiagram()) {
              return true;
              }
        else if (e->isSymbol() || e->isFSymbol()) {
              // symbols can be added in edit mode
              if (data.getData(this))
                    return true;
              else
                    return false;
              }
        return false;
        }
  
  //---------------------------------------------------------
  //   drop
  //---------------------------------------------------------
  
  Element* Harmony::drop(EditData& data)
        {
        Element* e = data.dropElement;
        if (e->isFretDiagram()) {
              FretDiagram* fd = toFretDiagram(e);
              fd->setParent(parent());
              fd->setTrack(track());
              score()->undoAddElement(fd);
              }
        else if (e->isSymbol() || e->isFSymbol()) {
              TextBase::drop(data);
              layout1();
              e = 0;      // cannot select
              }
        else {
              qWarning("Harmony: cannot drop <%s>\n", e->name());
              delete e;
              e = 0;
              }
        return e;
        }
  
  //---------------------------------------------------------
  //   getProperty
  //---------------------------------------------------------
  
  QVariant Harmony::getProperty(Pid pid) const
        {
        switch (pid) {
              case Pid::PLAY:
                    return QVariant(_play);
                    break;
              case Pid::HARMONY_TYPE:
                    return QVariant(int(_harmonyType));
                    break;
              case Pid::HARMONY_VOICE_LITERAL:
                    return _realizedHarmony.literal();
                    break;
              case Pid::HARMONY_VOICING:
                    return int(_realizedHarmony.voicing());
                    break;
              case Pid::HARMONY_DURATION:
                    return int(_realizedHarmony.duration());
                    break;
              default:
                    return TextBase::getProperty(pid);
              }
        }
  
  //---------------------------------------------------------
  //   setProperty
  //---------------------------------------------------------
  
  bool Harmony::setProperty(Pid pid, const QVariant& v)
        {
        switch (pid) {
              case Pid::PLAY:
                    setPlay(v.toBool());
                    break;
              case Pid::HARMONY_TYPE:
                    setHarmonyType(HarmonyType(v.toInt()));
                    break;
              case Pid::HARMONY_VOICE_LITERAL:
                    _realizedHarmony.setLiteral(v.toBool());
                    break;
              case Pid::HARMONY_VOICING:
                    _realizedHarmony.setVoicing(Voicing(v.toInt()));
                    break;
              case Pid::HARMONY_DURATION:
                    _realizedHarmony.setDuration(HDuration(v.toInt()));
                    break;
              default:
                    if (TextBase::setProperty(pid, v)) {
                          if (pid == Pid::TEXT)
                                setHarmony(v.toString());
                          render();
                          break;
                          }
                    return false;
              }
        return true;
        }
  
  //---------------------------------------------------------
  //   propertyDefault
  //---------------------------------------------------------
  
  QVariant Harmony::propertyDefault(Pid id) const
        {
        QVariant v;
        switch (id) {
              case Pid::HARMONY_TYPE:
                    v = int(HarmonyType::STANDARD);
                    break;
              case Pid::SUB_STYLE: {
                    switch (_harmonyType) {
                          case HarmonyType::STANDARD:
                                v = int(Tid::HARMONY_A);
                                break;
                          case HarmonyType::ROMAN:
                                v = int(Tid::HARMONY_ROMAN);
                                break;
                          case HarmonyType::NASHVILLE:
                                v = int(Tid::HARMONY_NASHVILLE);
                                break;
                          }
                    }
                    break;
              case Pid::OFFSET:
                    if (parent() && parent()->isFretDiagram()) {
                          v = QVariant(QPointF(0.0, 0.0));
                          break;
                          }
                    // fall-through
              default:
                    v = TextBase::propertyDefault(id);
                    break;
              }
        return v;
        }
  
  //---------------------------------------------------------
  //   getPropertyStyle
  //---------------------------------------------------------
  
  Sid Harmony::getPropertyStyle(Pid pid) const
        {
        if (pid == Pid::OFFSET) {
              if (parent() && parent()->isFretDiagram())
                    return Sid::NOSTYLE;
              else if (tid() == Tid::HARMONY_A)
                    return placeAbove() ? Sid::chordSymbolAPosAbove : Sid::chordSymbolAPosBelow;
              else if (tid() == Tid::HARMONY_B)
                    return placeAbove() ? Sid::chordSymbolBPosAbove : Sid::chordSymbolBPosBelow;
              else if (tid() == Tid::HARMONY_ROMAN)
                    return placeAbove() ? Sid::romanNumeralPosAbove : Sid::romanNumeralPosBelow;
              else if (tid() == Tid::HARMONY_NASHVILLE)
                    return placeAbove() ? Sid::nashvilleNumberPosAbove : Sid::nashvilleNumberPosBelow;
              }
        if (pid == Pid::PLACEMENT) {
              switch (_harmonyType) {
                    case HarmonyType::STANDARD:
                          return Sid::harmonyPlacement;
                    case HarmonyType::ROMAN:
                          return Sid::romanNumeralPlacement;
                    case HarmonyType::NASHVILLE:
                          return Sid::nashvilleNumberPlacement;
                    }
              }
        return TextBase::getPropertyStyle(pid);
        }
  
  }
  