MuseScore/libmscore/harmony.cpp
2014-03-03 17:04:20 +01:00

1349 lines
44 KiB
C++

//=============================================================================
// 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"
namespace Ms {
//---------------------------------------------------------
// harmonyName
//---------------------------------------------------------
QString Harmony::harmonyName()
{
determineRootBaseSpelling();
HChord hc = descr() ? descr()->chord : HChord();
QString s, r, e, b;
if (_leftParen)
s = "(";
if (_rootTpc != INVALID_TPC)
r = tpc2name(_rootTpc, _rootSpelling, _rootLowerCase);
if (_textName != "")
e = _textName.remove('=');
else if (!_degreeList.isEmpty()) {
hc.add(_degreeList);
// try to find the chord in chordList
const ChordDescription* newExtension = 0;
ChordList* cl = score()->style()->chordList();
foreach(const ChordDescription* cd, *cl) {
if (cd->chord == hc && !cd->names.isEmpty()) {
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 != INVALID_TPC)
b = "/" + tpc2name(_baseTpc, _baseSpelling, _baseLowerCase);
s += r + e + b;
if (_rightParen)
s += ")";
return s;
}
//---------------------------------------------------------
// rootName
//---------------------------------------------------------
QString Harmony::rootName()
{
determineRootBaseSpelling();
return tpc2name(_rootTpc, _rootSpelling, _rootLowerCase);
}
//---------------------------------------------------------
// baseName
//---------------------------------------------------------
QString Harmony::baseName()
{
determineRootBaseSpelling();
return tpc2name(_baseTpc, _baseSpelling, _baseLowerCase);
}
//---------------------------------------------------------
// resolveDegreeList
// try to detect chord number and to eliminate degree
// list
//---------------------------------------------------------
void Harmony::resolveDegreeList()
{
if (_degreeList.isEmpty())
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
ChordList* cl = score()->style()->chordList();
foreach(const ChordDescription* cd, *cl) {
if ((cd->chord == hc) && !cd->names.isEmpty()) {
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(s)
{
setTextStyleType(TEXT_STYLE_HARMONY);
setUnstyled();
_rootTpc = INVALID_TPC;
_baseTpc = INVALID_TPC;
_id = -1;
_parsedForm = 0;
_leftParen = false;
_rightParen = false;
}
Harmony::Harmony(const Harmony& h)
: Text(h)
{
_rootTpc = h._rootTpc;
_baseTpc = h._baseTpc;
_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(Xml& xml) const
{
xml.stag("Harmony");
if (_leftParen)
xml.tagE("leftParen");
if (_rootTpc != INVALID_TPC) {
int rRootTpc = _rootTpc;
int rBaseTpc = _baseTpc;
if (staff()) {
const Interval& interval = staff()->part()->instr()->transpose();
if (xml.clipboardmode && !score()->styleB(ST_concertPitch) && interval.chromatic) {
rRootTpc = transposeTpc(_rootTpc, interval, false);
rBaseTpc = transposeTpc(_baseTpc, interval, false);
}
}
xml.tag("root", rRootTpc);
if (_id > 0)
xml.tag("extension", _id);
if (_textName != "")
xml.tag("name", _textName);
if (rBaseTpc != INVALID_TPC)
xml.tag("base", rBaseTpc);
foreach(const HDegree& hd, _degreeList) {
int tp = hd.type();
if (tp == ADD || tp == ALTER || tp == SUBTRACT) {
xml.stag("degree");
xml.tag("degree-value", hd.value());
xml.tag("degree-alter", hd.alter());
switch (tp) {
case ADD:
xml.tag("degree-type", "add");
break;
case ALTER:
xml.tag("degree-type", "alter");
break;
case SUBTRACT:
xml.tag("degree-type", "subtract");
break;
default:
break;
}
xml.etag();
}
}
}
else
xml.tag("name", _textName);
Element::writeProperties(xml);
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 == "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 == "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, ADD));
else if (degreeType == "alter")
addDegree(HDegree(degreeValue, degreeAlter, ALTER));
else if (degreeType == "subtract")
addDegree(HDegree(degreeValue, degreeAlter, 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 != INVALID_TPC) {
if (_id > 0)
// lookup id in chord list and generate new description if necessary
getDescription();
else if (_textName != "")
// no id - look up name, in case it is in chord list with no id
getDescription(_textName);
}
else if (_textName == "")
_textName = text();
// render chord from description (or _textName)
render();
}
//---------------------------------------------------------
// determineRootBaseSpelling
//---------------------------------------------------------
void Harmony::determineRootBaseSpelling(NoteSpellingType& rootSpelling, bool& rootLowerCase, NoteSpellingType& baseSpelling, bool& baseLowerCase)
{
if (score()->styleB(ST_useStandardNoteNames))
rootSpelling = STANDARD;
else if (score()->styleB(ST_useGermanNoteNames))
rootSpelling = GERMAN;
else if (score()->styleB(ST_useSolfeggioNoteNames))
rootSpelling = SOLFEGGIO;
baseSpelling = rootSpelling;
const ChordDescription* cd = descr();
if (cd) {
QString quality;
quality = cd->quality();
if (score()->styleB(ST_lowerCaseMinorChords) && (quality == "minor" || quality == "diminished" || quality == "half-diminished"))
rootLowerCase = true;
else
rootLowerCase = false;
}
else
rootLowerCase = score()->styleB(ST_lowerCaseMinorChords);
if (baseSpelling == GERMAN)
baseLowerCase = true;
else
baseLowerCase = false;
}
//---------------------------------------------------------
// determineRootBaseSpelling
//---------------------------------------------------------
void Harmony::determineRootBaseSpelling()
{
determineRootBaseSpelling(_rootSpelling, _rootLowerCase, _baseSpelling, _baseLowerCase);
}
//---------------------------------------------------------
// convertRoot
// convert something like "C#" into tpc 21
//---------------------------------------------------------
static int convertRoot(const QString& s, NoteSpellingType spelling, int& idx)
{
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 INVALID_TPC;
int acci;
switch (spelling) {
case GERMAN: acci = 1; break;
case SOLFEGGIO: acci = 2; break;
default: acci = 1; break;
}
idx = acci;
int alter = 0;
int n = s.size();
QString acc = s.right(n-acci);
if (acc != "") {
if (acc.startsWith("b")) {
alter = -1;
idx += 1;
}
else if (spelling == GERMAN && acc.startsWith("es")) {
alter = -1;
idx += 2;
}
else if (spelling == GERMAN && acc.startsWith("s") && !acc.startsWith("su")) {
alter = -1;
idx += 1;
}
else if (acc.startsWith("#")) {
alter = 1;
idx += 1;
}
else if (spelling == GERMAN && acc.startsWith("is")) {
alter = 1;
idx += 2;
}
}
int r;
if (spelling == GERMAN) {
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)
return INVALID_TPC;
r = 6;
alter = -1;
break;
default:
return INVALID_TPC;
}
}
else if (spelling == SOLFEGGIO) {
QString ss = s.toLower().left(2);
if (ss == "do")
r = 0;
else if (ss == "re")
r = 1;
else if (ss == "mi")
r = 2;
else if (ss == "fa")
r = 3;
else if (ss == "sol")
r = 4;
else if (ss == "la")
r = 5;
else if (ss == "si")
r = 6;
else
return INVALID_TPC;
}
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 INVALID_TPC;
}
}
r = spellings[r * 5 + alter + 2];
return r;
}
//---------------------------------------------------------
// parseHarmony
// determine root and bass tpc
// 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 = convertRoot(s, _rootSpelling, idx);
if (r == INVALID_TPC) {
qDebug("1:parseHarmony failed <%s>", qPrintable(ss));
_userName = s;
_textName = s;
return 0;
}
*root = r;
bool preferMinor;
if (score()->styleB(ST_lowerCaseMinorChords) && s[0].isLower())
preferMinor = true;
else
preferMinor = false;
*base = INVALID_TPC;
int slash = s.lastIndexOf('/');
if (slash != -1) {
QString bs = s.mid(slash+1);
s = s.mid(idx, slash - idx).simplified();
int dummy;
*base = convertRoot(bs, _baseSpelling, dummy);
if (*base == INVALID_TPC)
// if no TPC after slash, reassemble chord
s = s + "/" + bs;
}
else
s = s.mid(idx).simplified();
_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);
if (preferMinor)
s = _parsedForm->name();
cd = descr(s, _parsedForm);
}
if (cd) {
_id = cd->id;
if (!cd->names.isEmpty())
_textName = cd->names.front();
}
else
_textName = s;
return cd;
}
//---------------------------------------------------------
// startEdit
//---------------------------------------------------------
void Harmony::startEdit(MuseScoreView* view, const QPointF& p)
{
if (!textList.isEmpty()) {
QString s(harmonyName());
setText(s);
Text::createLayout(); // create TextBlocks from text
}
Text::startEdit(view, p);
}
//---------------------------------------------------------
// edit
//---------------------------------------------------------
bool Harmony::edit(MuseScoreView* view, int grip, int key, Qt::KeyboardModifiers mod, const QString& s)
{
if (key == Qt::Key_Return)
return true; // Harmony only single line
bool rv = Text::edit(view, grip, key, mod, s);
QString str = text();
int root, base;
bool badSpell = !str.isEmpty() && !parseHarmony(str, &root, &base, true);
spellCheckUnderline(badSpell);
return rv;
}
//---------------------------------------------------------
// endEdit
//---------------------------------------------------------
void Harmony::endEdit()
{
Text::endEdit();
setHarmony(text());
layout();
if (links()) {
foreach(Element* e, *links()) {
if (e == this)
continue;
Harmony* h = static_cast<Harmony*>(e);
h->setHarmony(text());
}
}
score()->setLayoutAll(true);
}
//---------------------------------------------------------
// setHarmony
//---------------------------------------------------------
void Harmony::setHarmony(const QString& s)
{
int r, b;
const ChordDescription* cd = parseHarmony(s, &r, &b);
if (!cd && _parsedForm && _parsedForm->parseable()) {
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(INVALID_TPC);
setBaseTpc(INVALID_TPC);
_id = -1;
render();
}
}
//---------------------------------------------------------
// baseLine
//---------------------------------------------------------
qreal Harmony::baseLine() const
{
return (editMode() || textList.isEmpty()) ? Text::baseLine() : 0.0;
}
//---------------------------------------------------------
// text
//---------------------------------------------------------
QString HDegree::text() const
{
if (_type == UNDEF)
return QString();
const char* d = 0;
switch(_type) {
case UNDEF: break;
case ADD: d= "add"; break;
case ALTER: d= "alt"; break;
case 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();
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();
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.isEmpty())
_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 = new ChordDescription(_textName, cl);
cd->complete(_parsedForm, cl);
// remove parsed chord from description
// so we will only match it literally in the future
cd->parsedChords.clear();
cl->insert(cd->id, cd);
return cd;
}
//---------------------------------------------------------
// isEmpty
//---------------------------------------------------------
bool Harmony::isEmpty() const
{
return textList.isEmpty() && Text::isEmpty();
}
//---------------------------------------------------------
// 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;
if (parent()->type() == SEGMENT) {
Measure* m = static_cast<Measure*>(parent()->parent());
yy = track() < 0 ? 0.0 : m->system()->staff(staffIdx())->y();
yy -= score()->styleP(ST_harmonyY);
Segment* s = static_cast<Segment*>(parent());
qreal _spatium = spatium();
for (Element* e : s->annotations()) {
if (e != this && e->type() == FRET_DIAGRAM && e->track() == track()) {
yy += score()->styleP(ST_harmonyY);
yy -= score()->styleP(ST_fretY);
yy -= _spatium * 2;
yy -= score()->styleP(ST_harmonyFretDist);
break;
}
}
}
else if (parent()->type() == FRET_DIAGRAM)
yy = score()->styleP(ST_harmonyFretDist);
yy += textStyle().offset(spatium()).y();
if (!editMode()) {
qreal hb = lineHeight() - Text::baseLine();
if (textStyle().align() & ALIGN_BOTTOM)
yy -= hb;
else if (textStyle().align() & ALIGN_VCENTER) {
yy -= hb;
yy += (height() * .5);
}
else if (textStyle().align() & ALIGN_BASELINE) {
}
else { // ALIGN_TOP
yy -= hb;
yy += height();
}
}
qreal xx = textStyle().offset(spatium()).x();
if (!editMode()) {
qreal cw = symWidth(SymId::noteheadBlack);
if (textStyle().align() & ALIGN_RIGHT) {
xx += cw;
xx -= width();
}
else if (textStyle().align() & ALIGN_HCENTER) {
xx += (cw * .5);
xx -= (width() * .5);
}
}
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() == FRET_DIAGRAM && parent()->parent()->type() == SEGMENT) {
MStaff* mstaff = static_cast<Segment*>(parent()->parent())->measure()->mstaff(staffIdx());
qreal dist = -(bbox().top());
mstaff->distanceUp = qMax(mstaff->distanceUp, dist + spatium());
}
}
//---------------------------------------------------------
// calculateBoundingRect
//---------------------------------------------------------
void Harmony::calculateBoundingRect()
{
if (editMode() || textList.isEmpty()) {
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);
}
}
//---------------------------------------------------------
// shape
//---------------------------------------------------------
QPainterPath Harmony::shape() const
{
QPainterPath pp;
pp.addRect(bbox());
return pp;
}
//---------------------------------------------------------
// draw
//---------------------------------------------------------
void Harmony::draw(QPainter* painter) const
{
painter->setPen(curColor());
if (editMode() || textList.isEmpty()) {
Text::draw(painter);
return;
}
foreach(const TextSegment* ts, textList) {
painter->setFont(ts->font);
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);
qreal w = 0.0;
foreach(QChar c, text) {
w += fm.width(c);
}
return w;
}
//---------------------------------------------------------
// boundingRect
//---------------------------------------------------------
QRectF TextSegment::boundingRect() const
{
QFontMetricsF fm(font);
return fm.boundingRect(text);
}
//---------------------------------------------------------
// tightBoundingRect
//---------------------------------------------------------
QRectF TextSegment::tightBoundingRect() const
{
QFontMetricsF fm(font);
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 spelling, bool lowerCase)
{
ChordList* chordList = score()->style()->chordList();
QStack<QPointF> stack;
int fontIdx = 0;
qreal _spatium = spatium();
qreal mag = (MScore::DPI / PPI) * (_spatium / (SPATIUM20 * MScore::DPI));
foreach(const RenderAction& a, renderList) {
if (a.type == RenderAction::RENDER_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::RENDER_MOVE) {
x += a.movex * mag;
y += a.movey * mag;
}
else if (a.type == RenderAction::RENDER_PUSH)
stack.push(QPointF(x,y));
else if (a.type == RenderAction::RENDER_POP) {
if (!stack.isEmpty()) {
QPointF pt = stack.pop();
x = pt.x();
y = pt.y();
}
else
qDebug("RenderAction::RENDER_POP: stack empty");
}
else if (a.type == RenderAction::RENDER_NOTE) {
QString c;
int acc;
tpc2name(tpc, spelling, lowerCase, 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::RENDER_ACCIDENTAL) {
QString c;
QString acc;
tpc2name(tpc, spelling, lowerCase, c, acc);
if (acc != "") {
TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
ChordSymbol 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", a.type);
}
}
//---------------------------------------------------------
// render
// construct Chord Symbol
//---------------------------------------------------------
void Harmony::render(const TextStyle* st)
{
int capo = score()->styleI(ST_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->fontPx(spatium() * cf.mag));
else {
QFont ff(st->fontPx(spatium() * cf.mag));
ff.setFamily(cf.family);
fontList.append(ff);
}
}
if (fontList.isEmpty())
fontList.append(st->fontPx(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 != INVALID_TPC) {
// render root
render(chordList->renderListRoot, x, y, _rootTpc, _rootSpelling, _rootLowerCase);
// render extension
const ChordDescription* cd = getDescription();
if (cd)
render(cd->renderList, x, y, 0);
}
else
render(_textName, x, y);
// render bass
if (_baseTpc != INVALID_TPC)
render(chordList->renderListBase, x, y, _baseTpc, _baseSpelling, _baseLowerCase);
if (_rootTpc != INVALID_TPC && 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 != INVALID_TPC)
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 != INVALID_TPC && capoBassTpc < 6)) {
capoRootTpc += 12;
if (capoBassTpc != INVALID_TPC)
capoBassTpc += 12;
}
else if (capoRootTpc > 24 || (capoBassTpc != INVALID_TPC && capoBassTpc > 26)) {
capoRootTpc -= 12;
if (capoBassTpc != INVALID_TPC)
capoBassTpc -= 12;
}
render("(", x, y);
render(chordList->renderListRoot, x, y, capoRootTpc, _rootSpelling, _rootLowerCase);
// render extension
const ChordDescription* cd = getDescription();
if (cd)
render(cd->renderList, x, y, 0);
if (capoBassTpc != INVALID_TPC)
render(chordList->renderListBase, x, y, capoBassTpc, _baseSpelling, _baseLowerCase);
render(")", x, y);
}
if (_rightParen)
render(" )", x, y);
}
//---------------------------------------------------------
// spatiumChanged
//---------------------------------------------------------
void Harmony::spatiumChanged(qreal oldValue, qreal newValue)
{
Text::spatiumChanged(oldValue, newValue);
render();
}
//---------------------------------------------------------
// textStyleChanged
//---------------------------------------------------------
void Harmony::textStyleChanged()
{
Text::textStyleChanged();
render();
}
//---------------------------------------------------------
// dragAnchor
//---------------------------------------------------------
QLineF Harmony::dragAnchor() const
{
qreal xp = 0.0;
for (Element* e = parent(); e; e = e->parent())
xp += e->x();
qreal yp;
if (parent()->type() == SEGMENT)
yp = static_cast<Segment*>(parent())->measure()->system()->staffYpage(staffIdx());
else
yp = parent()->canvasPos().y();
QPointF p(xp, yp);
return QLineF(p, canvasPos());
}
//---------------------------------------------------------
// 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;
}
}