2026 lines
67 KiB
C++
2026 lines
67 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Music Composition & Notation
|
|
// $Id: chord.cpp 5658 2012-05-21 18:40:58Z wschweer $
|
|
//
|
|
// Copyright (C) 2002-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
|
|
//=============================================================================
|
|
|
|
/**
|
|
\file
|
|
Implementation of classes Chord, LedgerLine, NoteList Stem ans StemSlash.
|
|
*/
|
|
|
|
#include "chord.h"
|
|
#include "note.h"
|
|
#include "xml.h"
|
|
#include "style.h"
|
|
#include "segment.h"
|
|
#include "text.h"
|
|
#include "measure.h"
|
|
#include "system.h"
|
|
#include "tuplet.h"
|
|
#include "hook.h"
|
|
#include "slur.h"
|
|
#include "arpeggio.h"
|
|
#include "score.h"
|
|
#include "tremolo.h"
|
|
#include "glissando.h"
|
|
#include "staff.h"
|
|
#include "utils.h"
|
|
#include "articulation.h"
|
|
#include "undo.h"
|
|
#include "chordline.h"
|
|
#include "lyrics.h"
|
|
#include "navigate.h"
|
|
#include "stafftype.h"
|
|
#include "stem.h"
|
|
#include "mscore.h"
|
|
#include "accidental.h"
|
|
#include "noteevent.h"
|
|
#include "pitchspelling.h"
|
|
#include "rendermidi.h"
|
|
|
|
//---------------------------------------------------------
|
|
// StemSlash
|
|
//---------------------------------------------------------
|
|
|
|
StemSlash::StemSlash(Score* s)
|
|
: Element(s)
|
|
{
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// draw
|
|
//---------------------------------------------------------
|
|
|
|
void StemSlash::draw(QPainter* painter) const
|
|
{
|
|
qreal lw = point(score()->styleS(ST_stemWidth));
|
|
painter->setPen(QPen(curColor(), lw));
|
|
painter->drawLine(QLineF(line.x1(), line.y1(), line.x2(), line.y2()));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setLine
|
|
//---------------------------------------------------------
|
|
|
|
void StemSlash::setLine(const QLineF& l)
|
|
{
|
|
line = l;
|
|
qreal w = point(score()->styleS(ST_stemWidth)) * .5;
|
|
setbbox(QRectF(line.p1(), line.p2()).normalized().adjusted(-w, w, 2.0*w, 2.0*w));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layout
|
|
//---------------------------------------------------------
|
|
|
|
void StemSlash::layout()
|
|
{
|
|
Stem* stem = chord()->stem();
|
|
qreal h2;
|
|
qreal _spatium = spatium();
|
|
qreal l = chord()->up() ? _spatium : -_spatium;
|
|
QPointF p(stem->hookPos());
|
|
qreal x = p.x() + _spatium * .1;
|
|
qreal y = p.y();
|
|
|
|
if (chord()->beam()) {
|
|
y += l * .3;
|
|
h2 = l * .8;
|
|
}
|
|
else {
|
|
y += l * 1.2;
|
|
h2 = l * .4;
|
|
}
|
|
qreal w = chord()->upNote()->headWidth() * .7;
|
|
setLine(QLineF(QPointF(x + w, y - h2), QPointF(x - w, y + h2)));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// upLine / downLine
|
|
//---------------------------------------------------------
|
|
|
|
int Chord::upLine() const
|
|
{
|
|
return upNote()->line();
|
|
}
|
|
|
|
int Chord::downLine() const
|
|
{
|
|
return downNote()->line();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// upString / downString
|
|
//
|
|
// return the topmost / bottommost string used by chord
|
|
// Top and bottom refer to the DRAWN position, not the position in the instrument
|
|
// (i.e., upside-down TAB are taken into account)
|
|
//
|
|
// If no staff, always return 0
|
|
// If staf is not a TAB, always returns TOP and BOTTOM staff lines
|
|
//---------------------------------------------------------
|
|
|
|
int Chord::upString() const
|
|
{
|
|
// if no staff or staff not a TAB, return 0 (=topmost line)
|
|
if(!staff() || !staff()->isTabStaff())
|
|
return 0;
|
|
StaffTypeTablature* tab = (StaffTypeTablature*) staff()->staffType();
|
|
int line = tab->lines() - 1; // start at bottom line
|
|
int noteLine;
|
|
// scan each note: if TAB strings are not in sequential order,
|
|
// visual order of notes might not correspond to pitch order
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
noteLine = tab->physStringToVisual(_notes.at(i)->string());
|
|
if (noteLine < line)
|
|
line = noteLine;
|
|
}
|
|
return line;
|
|
}
|
|
|
|
int Chord::downString() const
|
|
{
|
|
if(!staff()) // if no staff, return 0
|
|
return 0;
|
|
if(!staff()->isTabStaff()) // if staff not a TAB, return bottom line
|
|
return staff()->lines()-1;
|
|
StaffTypeTablature* tab = (StaffTypeTablature*) staff()->staffType();
|
|
int line = 0; // start at top line
|
|
int noteLine;
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
noteLine = tab->physStringToVisual(_notes.at(i)->string());
|
|
if(noteLine > line)
|
|
line = noteLine;
|
|
}
|
|
return line;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Chord
|
|
//---------------------------------------------------------
|
|
|
|
Chord::Chord(Score* s)
|
|
: ChordRest(s)
|
|
{
|
|
_ledgerLines = 0;
|
|
_stem = 0;
|
|
_hook = 0;
|
|
_stemDirection = MScore::AUTO;
|
|
_arpeggio = 0;
|
|
_tremolo = 0;
|
|
_tremoloChordType = TremoloSingle;
|
|
_glissando = 0;
|
|
_noteType = NOTE_NORMAL;
|
|
_stemSlash = 0;
|
|
_noStem = false;
|
|
_userPlayEvents = false;
|
|
setFlags(ELEMENT_MOVABLE | ELEMENT_ON_STAFF);
|
|
}
|
|
|
|
Chord::Chord(const Chord& c)
|
|
: ChordRest(c)
|
|
{
|
|
_ledgerLines = 0;
|
|
int n = c._notes.size();
|
|
for (int i = 0; i < n; ++i)
|
|
add(new Note(*c._notes.at(i)));
|
|
|
|
_stem = 0;
|
|
_hook = 0;
|
|
_glissando = 0;
|
|
_arpeggio = 0;
|
|
_stemSlash = 0;
|
|
|
|
_noStem = c._noStem;
|
|
_userPlayEvents = c._userPlayEvents;
|
|
|
|
if (c._stem)
|
|
add(new Stem(*(c._stem)));
|
|
if (c._hook)
|
|
add(new Hook(*(c._hook)));
|
|
if (c._glissando)
|
|
add(new Glissando(*(c._glissando)));
|
|
if (c._arpeggio)
|
|
add(new Arpeggio(*(c._arpeggio)));
|
|
if (c._stemSlash)
|
|
add(new StemSlash(*(c._stemSlash)));
|
|
_stemDirection = c._stemDirection;
|
|
_tremoloChordType = TremoloSingle;
|
|
_tremolo = 0;
|
|
_noteType = c._noteType;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// linkedClone
|
|
//---------------------------------------------------------
|
|
|
|
Chord* Chord::linkedClone()
|
|
{
|
|
Chord* chord = new Chord(*this);
|
|
linkTo(chord);
|
|
int n = notes().size();
|
|
for (int i = 0; i < n; ++i)
|
|
_notes[i]->linkTo(chord->_notes[i]);
|
|
return chord;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setScore
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::setScore(Score* s)
|
|
{
|
|
Element::setScore(s);
|
|
int n = notes().size();
|
|
for (int i = 0; i < n; ++i)
|
|
_notes[i]->setScore(s);
|
|
if (_stem)
|
|
_stem->setScore(s);
|
|
if (_hook)
|
|
_hook->setScore(s);
|
|
if (_glissando)
|
|
_glissando->setScore(s);
|
|
if (_arpeggio)
|
|
_arpeggio->setScore(s);
|
|
if (_stemSlash)
|
|
_stemSlash->setScore(s);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// ~Chord
|
|
//---------------------------------------------------------
|
|
|
|
Chord::~Chord()
|
|
{
|
|
delete _arpeggio;
|
|
if (_tremolo && _tremolo->chord1() == this)
|
|
delete _tremolo;
|
|
delete _glissando;
|
|
delete _stemSlash;
|
|
delete _stem;
|
|
delete _hook;
|
|
for (LedgerLine* ll = _ledgerLines; ll; ll = ll->next())
|
|
delete ll;
|
|
qDeleteAll(_notes);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setStem
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::setStem(Stem* s)
|
|
{
|
|
delete _stem;
|
|
_stem = s;
|
|
if (_stem) {
|
|
_stem->setParent(this);
|
|
_stem->setTrack(track());
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// stemPos
|
|
// return page coordinates
|
|
//---------------------------------------------------------
|
|
|
|
QPointF Chord::stemPos() const
|
|
{
|
|
if (staff() && staff()->isTabStaff())
|
|
return (static_cast<StaffTypeTablature*>(staff()->staffType())->chordStemPos(this) * spatium()) + pagePos();
|
|
return (_up ? downNote() : upNote())->stemPos(_up);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// stemPosX
|
|
// return page coordinates
|
|
//---------------------------------------------------------
|
|
|
|
qreal Chord::stemPosX() const
|
|
{
|
|
qreal x = pageX();
|
|
if (staff() && staff()->isTabStaff()) {
|
|
QPointF p = static_cast<StaffTypeTablature*>(staff()->staffType())->chordStemPos(this) * spatium();
|
|
x += p.x();
|
|
}
|
|
else if (_up)
|
|
x += symbols[score()->symIdx()][quartheadSym].width(magS());
|
|
return x;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// stemPosBeam
|
|
// return stem position of note on beam side
|
|
// return canvas coordinates
|
|
//---------------------------------------------------------
|
|
|
|
QPointF Chord::stemPosBeam() const
|
|
{
|
|
if (staff() && staff()->isTabStaff())
|
|
return (static_cast<StaffTypeTablature*>(staff()->staffType())->chordStemPosBeam(this) * spatium()) + pagePos();
|
|
return (_up ? upNote() : downNote())->stemPos(_up);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setSelected
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::setSelected(bool f)
|
|
{
|
|
Element::setSelected(f);
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i)
|
|
_notes.at(i)->setSelected(f);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// add
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::add(Element* e)
|
|
{
|
|
e->setParent(this);
|
|
e->setTrack(track());
|
|
switch(e->type()) {
|
|
case NOTE:
|
|
{
|
|
Note* note = static_cast<Note*>(e);
|
|
bool found = false;
|
|
for (int idx = 0; idx < _notes.size(); ++idx) {
|
|
if (note->pitch() < _notes[idx]->pitch()) {
|
|
_notes.insert(idx, note);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
_notes.append(note);
|
|
if (note->tieFor()) {
|
|
if (note->tieFor()->endNote())
|
|
note->tieFor()->endNote()->setTieBack(note->tieFor());
|
|
}
|
|
}
|
|
break;
|
|
case ARPEGGIO:
|
|
_arpeggio = static_cast<Arpeggio*>(e);
|
|
break;
|
|
case TREMOLO:
|
|
{
|
|
Tremolo* tr = static_cast<Tremolo*>(e);
|
|
if (tr->twoNotes()) {
|
|
if (!(_tremolo && _tremolo->twoNotes())) {
|
|
TDuration d = durationType();
|
|
int dots = d.dots();
|
|
d = d.shift(-1);
|
|
d.setDots(dots);
|
|
if (tr->chord1())
|
|
tr->chord1()->setDurationType(d);
|
|
if (tr->chord2())
|
|
tr->chord2()->setDurationType(d);
|
|
}
|
|
_tremoloChordType = TremoloFirstNote;
|
|
tr->chord2()->setTremolo(tr);
|
|
tr->chord2()->setTremoloChordType(TremoloSecondNote);
|
|
}
|
|
else
|
|
_tremoloChordType = TremoloSingle;
|
|
_tremolo = tr;
|
|
}
|
|
break;
|
|
case GLISSANDO:
|
|
_glissando = static_cast<Glissando*>(e);
|
|
break;
|
|
case STEM:
|
|
_stem = static_cast<Stem*>(e);
|
|
break;
|
|
case HOOK:
|
|
_hook = static_cast<Hook*>(e);
|
|
break;
|
|
case CHORDLINE:
|
|
_el.append(e);
|
|
break;
|
|
case STEM_SLASH:
|
|
_stemSlash = static_cast<StemSlash*>(e);
|
|
_stemSlash->setMag(mag());
|
|
break;
|
|
default:
|
|
ChordRest::add(e);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// remove
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::remove(Element* e)
|
|
{
|
|
switch(e->type()) {
|
|
case NOTE:
|
|
{
|
|
Note* note = static_cast<Note*>(e);
|
|
if (_notes.removeOne(note)) {
|
|
if (note->tieFor()) {
|
|
if (note->tieFor()->endNote())
|
|
note->tieFor()->endNote()->setTieBack(0);
|
|
}
|
|
}
|
|
else
|
|
qDebug("Chord::remove() note %p not found!\n", e);
|
|
}
|
|
break;
|
|
|
|
case ARPEGGIO:
|
|
_arpeggio = 0;
|
|
break;
|
|
case TREMOLO:
|
|
{
|
|
Tremolo* tremolo = static_cast<Tremolo*>(e);
|
|
if (tremolo->twoNotes()) {
|
|
TDuration d = durationType();
|
|
int dots = d.dots();
|
|
d = d.shift(1);
|
|
d.setDots(dots);
|
|
if (tremolo->chord1())
|
|
tremolo->chord1()->setDurationType(d);
|
|
if (tremolo->chord2())
|
|
tremolo->chord2()->setDurationType(d);
|
|
tremolo->chord2()->setTremolo(0);
|
|
}
|
|
_tremolo = 0;
|
|
}
|
|
break;
|
|
case GLISSANDO:
|
|
_glissando = 0;
|
|
break;
|
|
case STEM:
|
|
_stem = 0;
|
|
break;
|
|
case HOOK:
|
|
_hook = 0;
|
|
break;
|
|
case CHORDLINE:
|
|
_el.removeOne(e);
|
|
break;
|
|
default:
|
|
ChordRest::remove(e);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// addLedgerLine
|
|
/// Add a ledger line to a chord.
|
|
/// \arg x note head position
|
|
/// \arg staffIdx determines the y origin
|
|
/// \arg line vertical position of line
|
|
/// \arg lr extend to left and/or right
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::addLedgerLine(qreal x, int staffIdx, int line, int lr, bool visible)
|
|
{
|
|
qreal _spatium = spatium();
|
|
qreal hw = upNote()->headWidth();
|
|
qreal hw2 = hw * .5;
|
|
|
|
LedgerLine* h = new LedgerLine(score());
|
|
|
|
h->setParent(this);
|
|
h->setTrack(staff2track(staffIdx));
|
|
if (staff()->invisible())
|
|
visible = false;
|
|
h->setVisible(visible);
|
|
|
|
// ledger lines extend less than half a space on each side
|
|
// of the notehead:
|
|
//
|
|
qreal ll = hw + score()->styleS(ST_ledgerLineLength).val() * _spatium;
|
|
|
|
if (_noteType != NOTE_NORMAL)
|
|
ll *= score()->style(ST_graceNoteMag).toDouble();
|
|
x -= ll * .5;
|
|
|
|
x += (lr & 1) ? -hw2 : hw2;
|
|
if (lr == 3)
|
|
ll += hw;
|
|
|
|
Spatium len(ll / _spatium);
|
|
|
|
//
|
|
// Experimental:
|
|
// shorten ledger line to avoid collisions with accidentals
|
|
//
|
|
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
const Note* n = _notes.at(i);
|
|
if (n->line() >= (line-1) && n->line() <= (line+1) && n->accidental()) {
|
|
x += _spatium * .25;
|
|
len -= Spatium(.25);
|
|
break;
|
|
}
|
|
}
|
|
h->setLen(len);
|
|
h->setPos(x, line * _spatium * .5);
|
|
h->setNext(_ledgerLines);
|
|
_ledgerLines = h;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// addLedgerLines
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::addLedgerLines(qreal x, int move)
|
|
{
|
|
int uppos = 1000;
|
|
int ulr = 0;
|
|
int idx = staffIdx() + move;
|
|
|
|
// make ledger lines invisible if all notes
|
|
// are invisible
|
|
bool visible = false;
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
if (_notes.at(i)->visible()) {
|
|
visible = true;
|
|
break;
|
|
}
|
|
}
|
|
for (int ni = n - 1; ni >= 0; --ni) {
|
|
const Note* note = _notes[ni];
|
|
int l = note->line();
|
|
if (l >= 0)
|
|
break;
|
|
for (int i = (uppos+1) & ~1; i < l; i += 2)
|
|
addLedgerLine(x, idx, i, ulr, visible);
|
|
ulr |= (up() ^ note->mirror()) ? 0x1 : 0x2;
|
|
uppos = l;
|
|
}
|
|
for (int i = (uppos+1) & ~1; i <= -2; i += 2)
|
|
addLedgerLine(x, idx, i, ulr, visible);
|
|
|
|
int downpos = -1000;
|
|
int dlr = 0;
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
const Note* note = _notes.at(i);
|
|
int l = note->line();
|
|
if (l <= 8)
|
|
break;
|
|
for (int i = downpos & ~1; i > l; i -= 2)
|
|
addLedgerLine(x, idx, i, dlr, visible);
|
|
dlr |= (up() ^ note->mirror()) ? 0x1 : 0x2;
|
|
downpos = l;
|
|
}
|
|
for (int i = downpos & ~1; i >= 10; i -= 2)
|
|
addLedgerLine(x, idx, i, dlr, visible);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// computeUp
|
|
// rules:
|
|
// single note:
|
|
// All notes beneath the middle line: upward stems
|
|
// All notes on or above the middle line: downward stems
|
|
// two notes:
|
|
// If the interval above the middle line is greater than the interval
|
|
// below the middle line: downward stems
|
|
// If the interval below the middle line is greater than the interval
|
|
// above the middle line: upward stems
|
|
// If the two notes are the same distance from the middle line:
|
|
// stem can go in either direction. but most engravers prefer
|
|
// downward stems
|
|
// > two notes:
|
|
// If the interval of the highest note above the middle line is greater
|
|
// than the interval of the lowest note below the middle line:
|
|
// downward stems
|
|
// If the interval of the lowest note below the middle line is greater
|
|
// than the interval of the highest note above the middle line:
|
|
// upward stem
|
|
// If the highest and the lowest notes are the same distance from
|
|
// the middle line:, use these rules to determine stem direction:
|
|
// - If the majority of the notes are above the middle:
|
|
// downward stems
|
|
// - If the majority of the notes are below the middle:
|
|
// upward stems
|
|
// exception: in tablatures all stems are up.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void Chord::computeUp()
|
|
{
|
|
// tablatures
|
|
if (staff() && staff()->isTabStaff()) {
|
|
_up = !((StaffTypeTablature*)staff()->staffType())->stemsDown();
|
|
return;
|
|
}
|
|
// pitched staves
|
|
if (_stemDirection != MScore::AUTO) {
|
|
_up = _stemDirection == MScore::UP;
|
|
}
|
|
else if (_noteType != NOTE_NORMAL) {
|
|
//
|
|
// stem direction for grace notes
|
|
//
|
|
if (measure()->mstaff(staffIdx())->hasVoices) {
|
|
switch(voice()) {
|
|
case 0: _up = (score()->style(ST_stemDir1).toDirection() == MScore::UP); break;
|
|
case 1: _up = (score()->style(ST_stemDir2).toDirection() == MScore::UP); break;
|
|
case 2: _up = (score()->style(ST_stemDir3).toDirection() == MScore::UP); break;
|
|
case 3: _up = (score()->style(ST_stemDir4).toDirection() == MScore::UP); break;
|
|
}
|
|
}
|
|
else
|
|
_up = true;
|
|
}
|
|
else if (staffMove()) {
|
|
_up = staffMove() > 0;
|
|
}
|
|
else if (measure()->mstaff(staffIdx())->hasVoices) {
|
|
switch(voice()) {
|
|
case 0: _up = (score()->style(ST_stemDir1).toDirection() == MScore::UP); break;
|
|
case 1: _up = (score()->style(ST_stemDir2).toDirection() == MScore::UP); break;
|
|
case 2: _up = (score()->style(ST_stemDir3).toDirection() == MScore::UP); break;
|
|
case 3: _up = (score()->style(ST_stemDir4).toDirection() == MScore::UP); break;
|
|
}
|
|
}
|
|
else if (_notes.size() == 1 || staffMove()) {
|
|
if (staffMove() > 0)
|
|
_up = true;
|
|
else if (staffMove() < 0)
|
|
_up = false;
|
|
else
|
|
_up = upNote()->line() > 4;
|
|
}
|
|
else {
|
|
Note* un = upNote();
|
|
Note* dn = downNote();
|
|
int ud = un->line() - 4;
|
|
int dd = dn->line() - 4;
|
|
if (-ud == dd) {
|
|
int up = 0;
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
const Note* n = _notes.at(i);
|
|
int l = n->line();
|
|
if (l <= 4)
|
|
--up;
|
|
else
|
|
++up;
|
|
}
|
|
_up = up > 0;
|
|
}
|
|
else
|
|
_up = dd > -ud;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// selectedNote
|
|
//---------------------------------------------------------
|
|
|
|
Note* Chord::selectedNote() const
|
|
{
|
|
Note* note = 0;
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
Note* n = _notes.at(i);
|
|
if (n->selected()) {
|
|
if (note)
|
|
return 0;
|
|
note = n;
|
|
}
|
|
}
|
|
return note;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Chord::write
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::write(Xml& xml) const
|
|
{
|
|
xml.stag("Chord");
|
|
ChordRest::writeProperties(xml);
|
|
if (_noteType != NOTE_NORMAL) {
|
|
switch(_noteType) {
|
|
case NOTE_INVALID:
|
|
case NOTE_NORMAL:
|
|
break;
|
|
case NOTE_ACCIACCATURA:
|
|
xml.tagE("acciaccatura");
|
|
break;
|
|
case NOTE_APPOGGIATURA:
|
|
xml.tagE("appoggiatura");
|
|
break;
|
|
case NOTE_GRACE4:
|
|
xml.tagE("grace4");
|
|
break;
|
|
case NOTE_GRACE16:
|
|
xml.tagE("grace16");
|
|
break;
|
|
case NOTE_GRACE32:
|
|
xml.tagE("grace32");
|
|
break;
|
|
}
|
|
}
|
|
if (_noStem)
|
|
xml.tag("noStem", _noStem);
|
|
else if (_stem && (!_stem->userOff().isNull() || (_stem->userLen() != 0.0) || !_stem->visible() || (_stem->color() != MScore::defaultColor)))
|
|
_stem->write(xml);
|
|
if (_hook && (!_hook->visible() || !_hook->userOff().isNull() || (_hook->color() != MScore::defaultColor)))
|
|
_hook->write(xml);
|
|
switch(_stemDirection) {
|
|
case MScore::UP: xml.tag("StemDirection", QVariant("up")); break;
|
|
case MScore::DOWN: xml.tag("StemDirection", QVariant("down")); break;
|
|
case MScore::AUTO: break;
|
|
}
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i)
|
|
_notes.at(i)->write(xml);
|
|
if (_arpeggio)
|
|
_arpeggio->write(xml);
|
|
if (_glissando)
|
|
_glissando->write(xml);
|
|
if (_tremolo)
|
|
_tremolo->write(xml);
|
|
n = _el.size();
|
|
for (int i = 0; i < n; ++i)
|
|
_el.at(i)->write(xml);
|
|
xml.etag();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Chord::readNote
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::readNote(XmlReader& e)
|
|
{
|
|
Note* note = new Note(score());
|
|
note->setPitch(e.intAttribute("pitch"));
|
|
if (e.hasAttribute("ticks")) {
|
|
int ticks = e.intAttribute("ticks");
|
|
TDuration d;
|
|
d.setVal(ticks);
|
|
setDurationType(d);
|
|
}
|
|
int tpc = INVALID_TPC;
|
|
if (e.hasAttribute("tpc"))
|
|
tpc = e.intAttribute("tpc");
|
|
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
|
|
QString val(e.readElementText());
|
|
if (tag == "StemDirection") {
|
|
if (val == "up")
|
|
_stemDirection = MScore::UP;
|
|
else if (val == "down")
|
|
_stemDirection = MScore::DOWN;
|
|
else
|
|
_stemDirection = MScore::Direction(e.readInt());
|
|
}
|
|
else if (tag == "pitch")
|
|
note->setPitch(e.readInt());
|
|
else if (tag == "prefix") {
|
|
qDebug("read Note:: prefix: TODO\n");
|
|
}
|
|
else if (tag == "line")
|
|
note->setLine(e.readInt());
|
|
else if (tag == "Tie") {
|
|
Tie* _tieFor = new Tie(score());
|
|
_tieFor->setTrack(track());
|
|
_tieFor->read(e);
|
|
_tieFor->setStartNote(note);
|
|
note->setTieFor(_tieFor);
|
|
}
|
|
else if (tag == "Text") {
|
|
Text* f = new Text(score());
|
|
f->setTextStyle(score()->textStyle(TEXT_STYLE_FINGERING));
|
|
f->read(e);
|
|
f->setParent(this);
|
|
note->add(f);
|
|
}
|
|
else if (tag == "move")
|
|
setStaffMove(e.readInt());
|
|
else if (!ChordRest::readProperties(e))
|
|
e.unknown();
|
|
}
|
|
if (!tpcIsValid(tpc))
|
|
note->setTpc(tpc);
|
|
else
|
|
note->setTpcFromPitch();
|
|
add(note);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Chord::read
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::read(XmlReader& e)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
const QStringRef& tag(e.name());
|
|
|
|
if (tag == "Note") {
|
|
Note* note = new Note(score());
|
|
// the note needs to know the properties of the track it belongs to
|
|
note->setTrack(track());
|
|
note->setChord(this);
|
|
note->read(e);
|
|
add(note);
|
|
}
|
|
else if (tag == "appoggiatura") {
|
|
_noteType = NOTE_APPOGGIATURA;
|
|
e.readNext();
|
|
}
|
|
else if (tag == "acciaccatura") {
|
|
_noteType = NOTE_ACCIACCATURA;
|
|
e.readNext();
|
|
}
|
|
else if (tag == "grace4") {
|
|
_noteType = NOTE_GRACE4;
|
|
e.readNext();
|
|
}
|
|
else if (tag == "grace16") {
|
|
_noteType = NOTE_GRACE16;
|
|
e.readNext();
|
|
}
|
|
else if (tag == "grace32") {
|
|
_noteType = NOTE_GRACE32;
|
|
e.readNext();
|
|
}
|
|
else if (tag == "StemDirection") {
|
|
QString val(e.readElementText());
|
|
if (val == "up")
|
|
_stemDirection = MScore::UP;
|
|
else if (val == "down")
|
|
_stemDirection = MScore::DOWN;
|
|
else
|
|
_stemDirection = MScore::Direction(val.toInt());
|
|
}
|
|
else if (tag == "noStem")
|
|
_noStem = e.readInt();
|
|
else if (tag == "Arpeggio") {
|
|
_arpeggio = new Arpeggio(score());
|
|
_arpeggio->setTrack(track());
|
|
_arpeggio->read(e);
|
|
_arpeggio->setParent(this);
|
|
}
|
|
else if (tag == "Glissando") {
|
|
_glissando = new Glissando(score());
|
|
_glissando->setTrack(track());
|
|
_glissando->read(e);
|
|
_glissando->setParent(this);
|
|
}
|
|
else if (tag == "Tremolo") {
|
|
_tremolo = new Tremolo(score());
|
|
_tremolo->setTrack(track());
|
|
_tremolo->read(e);
|
|
_tremolo->setParent(this);
|
|
}
|
|
else if (tag == "tickOffset") // obsolete
|
|
;
|
|
else if (tag == "Stem") {
|
|
_stem = new Stem(score());
|
|
_stem->read(e);
|
|
add(_stem);
|
|
}
|
|
else if (tag == "Hook") {
|
|
_hook = new Hook(score());
|
|
_hook->read(e);
|
|
add(_hook);
|
|
}
|
|
else if (tag == "ChordLine") {
|
|
ChordLine* cl = new ChordLine(score());
|
|
cl->read(e);
|
|
add(cl);
|
|
}
|
|
else if (!ChordRest::readProperties(e))
|
|
e.unknown();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// upPos
|
|
//---------------------------------------------------------
|
|
|
|
qreal Chord::upPos() const
|
|
{
|
|
return upNote()->pos().y();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// downPos
|
|
//---------------------------------------------------------
|
|
|
|
qreal Chord::downPos() const
|
|
{
|
|
return downNote()->pos().y();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// centerX
|
|
// return x position for attributes
|
|
//---------------------------------------------------------
|
|
|
|
qreal Chord::centerX() const
|
|
{
|
|
const Note* note = up() ? upNote() : downNote();
|
|
qreal x = note->pos().x();
|
|
x += note->headWidth() * .5;
|
|
if (note->mirror()) {
|
|
x += note->headWidth() * (up() ? -1.0 : 1.0);
|
|
}
|
|
return x;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// scanElements
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::scanElements(void* data, void (*func)(void*, Element*), bool all)
|
|
{
|
|
if (_hook)
|
|
func(data, _hook);
|
|
if (_stem)
|
|
func(data, _stem);
|
|
if (_stemSlash)
|
|
func(data, _stemSlash);
|
|
if (_arpeggio)
|
|
func(data, _arpeggio);
|
|
if (_tremolo && (_tremoloChordType != TremoloSecondNote))
|
|
func(data, _tremolo);
|
|
if (_glissando)
|
|
func(data, _glissando);
|
|
for (LedgerLine* ll = _ledgerLines; ll; ll = ll->next())
|
|
func(data, ll);
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i)
|
|
_notes.at(i)->scanElements(data, func, all);
|
|
n = _el.size();
|
|
for (int i = 0; i < n; ++i)
|
|
_el.at(i)->scanElements(data, func, all);
|
|
ChordRest::scanElements(data, func, all);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setTrack
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::setTrack(int val)
|
|
{
|
|
if (_hook)
|
|
_hook->setTrack(val);
|
|
if (_stem)
|
|
_stem->setTrack(val);
|
|
if (_stemSlash)
|
|
_stemSlash->setTrack(val);
|
|
if (_arpeggio)
|
|
_arpeggio->setTrack(val);
|
|
if (_glissando)
|
|
_glissando->setTrack(val);
|
|
if (_tremolo)
|
|
_tremolo->setTrack(val);
|
|
|
|
for (LedgerLine* ll = _ledgerLines; ll; ll = ll->next())
|
|
ll->setTrack(val);
|
|
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i)
|
|
_notes.at(i)->setTrack(val);
|
|
ChordRest::setTrack(val);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// LedgerLine
|
|
//---------------------------------------------------------
|
|
|
|
LedgerLine::LedgerLine(Score* s)
|
|
: Line(s, false)
|
|
{
|
|
setZ(NOTE * 100 - 50);
|
|
setSelectable(false);
|
|
_next = 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// pagePos
|
|
//---------------------------------------------------------
|
|
|
|
QPointF LedgerLine::pagePos() const
|
|
{
|
|
System* system = chord()->measure()->system();
|
|
qreal yp = y() + system->staff(staffIdx())->y() + system->y();
|
|
return QPointF(pageX(), yp);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// measureXPos
|
|
//---------------------------------------------------------
|
|
|
|
qreal LedgerLine::measureXPos() const
|
|
{
|
|
qreal xp = x(); // chord relative
|
|
xp += chord()->x(); // segment relative
|
|
xp += chord()->segment()->x(); // measure relative
|
|
return xp;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layout
|
|
//---------------------------------------------------------
|
|
|
|
void LedgerLine::layout()
|
|
{
|
|
setLineWidth(score()->styleS(ST_ledgerLineWidth));
|
|
Line::layout();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setMag
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::setMag(qreal val)
|
|
{
|
|
ChordRest::setMag(val);
|
|
for (LedgerLine* ll = _ledgerLines; ll; ll = ll->next())
|
|
ll->setMag(val);
|
|
if (_stem)
|
|
_stem->setMag(val);
|
|
if (_hook)
|
|
_hook->setMag(val);
|
|
if (_stemSlash)
|
|
_stemSlash->setMag(val);
|
|
if (_arpeggio)
|
|
_arpeggio->setMag(val);
|
|
if (_tremolo)
|
|
_tremolo->setMag(val);
|
|
if (_glissando)
|
|
_glissando->setMag(val);
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i)
|
|
_notes.at(i)->setMag(val);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layoutStem1
|
|
/// Layout chord stem and hook.
|
|
//
|
|
// called before layout spacing of notes
|
|
// set hook if necessary to get right note width for next
|
|
// pass
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::layoutStem1()
|
|
{
|
|
int istaff = staffIdx();
|
|
|
|
//-----------------------------------------
|
|
// process stem
|
|
//-----------------------------------------
|
|
|
|
bool hasStem = durationType().hasStem() && !(_noStem || measure()->slashStyle(istaff));
|
|
int hookIdx = hasStem ? durationType().hooks() : 0;
|
|
|
|
if (hasStem) {
|
|
if (!_stem)
|
|
setStem(new Stem(score()));
|
|
}
|
|
else
|
|
setStem(0);
|
|
|
|
if (hasStem && (_noteType == NOTE_ACCIACCATURA)) {
|
|
if (_stemSlash == 0)
|
|
add(new StemSlash(score()));
|
|
}
|
|
else
|
|
setStemSlash(0);
|
|
|
|
//-----------------------------------------
|
|
// process hook
|
|
//-----------------------------------------
|
|
|
|
if (hookIdx) {
|
|
if (!up())
|
|
hookIdx = -hookIdx;
|
|
if (!_hook) {
|
|
Hook* hook = new Hook(score());
|
|
hook->setParent(this);
|
|
score()->undoAddElement(hook);
|
|
}
|
|
_hook->setMag(mag());
|
|
_hook->setSubtype(hookIdx);
|
|
}
|
|
else if (_hook)
|
|
score()->undoRemoveElement(_hook);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layoutStem
|
|
/// Layout chord tremolo stem and hook.
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::layoutStem()
|
|
{
|
|
//
|
|
// TAB
|
|
//
|
|
if (staff() && staff()->isTabStaff()) {
|
|
StaffTypeTablature* tab = (StaffTypeTablature*)staff()->staffType();
|
|
// require stems only if not stemless and this chord has a stem
|
|
if (!tab->slashStyle() && _stem) {
|
|
// process stem:
|
|
_stem->setPos(tab->chordStemPos(this) * spatium());
|
|
_stem->setLen(tab->chordStemLength(this) * spatium());
|
|
// process hook
|
|
int hookIdx = durationType().hooks();
|
|
if (tab->stemsDown())
|
|
hookIdx = -hookIdx;
|
|
if (hookIdx) {
|
|
_hook->setSubtype(hookIdx);
|
|
_hook->setPos(_stem->pos().x(), _stem->pos().y() + (up() ? -_stem->len() : _stem->len()));
|
|
_hook->setMag(mag()*score()->styleD(ST_smallNoteMag));
|
|
_hook->adjustReadPos();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// NON-TAB
|
|
//
|
|
if (segment()) {
|
|
System* s = segment()->measure()->system();
|
|
if (s == 0) //DEBUG
|
|
return;
|
|
}
|
|
|
|
if (_stem) {
|
|
qreal _spatium = spatium();
|
|
qreal stemLen;
|
|
int hookIdx = durationType().hooks();
|
|
Note* upnote = upNote();
|
|
Note* downnote = downNote();
|
|
int ul = upnote->line();
|
|
int dl = downnote->line();
|
|
bool shortenStem = score()->styleB(ST_shortenStem);
|
|
if (hookIdx >= 2 || _tremolo)
|
|
shortenStem = false;
|
|
|
|
Spatium progression(score()->styleS(ST_shortStemProgression));
|
|
qreal shortest(score()->styleS(ST_shortestStem).val());
|
|
|
|
qreal normalStemLen = small() ? 2.5 : 3.5;
|
|
switch(hookIdx) {
|
|
case 3: normalStemLen += small() ? .5 : 0.75; break; //32nd notes
|
|
case 4: normalStemLen += small() ? 1.0 : 1.5; break; //64th notes
|
|
case 5: normalStemLen += small() ? 1.5 : 2.25; break; //128th notes
|
|
}
|
|
if (_hook) {
|
|
if (up() && durationType().dots()) {
|
|
//
|
|
// avoid collision of dot with hook
|
|
//
|
|
if (!(ul & 1))
|
|
normalStemLen += .5;
|
|
shortenStem = false;
|
|
}
|
|
}
|
|
|
|
if (_noteType != NOTE_NORMAL) {
|
|
// grace notes stems are not subject to normal
|
|
// stem rules
|
|
stemLen = qAbs(ul - dl) * .5;
|
|
stemLen += normalStemLen * score()->styleD(ST_graceNoteMag);
|
|
if (up())
|
|
stemLen *= -1;
|
|
}
|
|
else {
|
|
if (up()) {
|
|
qreal dy = dl * .5;
|
|
qreal sel = ul * .5 - normalStemLen;
|
|
|
|
if (shortenStem && (sel < 0.0) && (hookIdx == 0 || !downnote->mirror()))
|
|
sel -= sel * progression.val();
|
|
if (sel > 2.0)
|
|
sel = 2.0;
|
|
stemLen = sel - dy;
|
|
if (-stemLen < shortest)
|
|
stemLen = -shortest;
|
|
}
|
|
else {
|
|
qreal uy = ul * .5;
|
|
qreal sel = dl * .5 + normalStemLen;
|
|
|
|
if (shortenStem && (sel > 4.0) && (hookIdx == 0 || downnote->mirror()))
|
|
sel -= (sel - 4.0) * progression.val();
|
|
if (sel < 2.0)
|
|
sel = 2.0;
|
|
stemLen = sel - uy;
|
|
if (stemLen < shortest)
|
|
stemLen = shortest;
|
|
}
|
|
}
|
|
|
|
// adjust stem len for tremolo
|
|
if (_tremolo && !_tremolo->twoNotes()) {
|
|
// hook up odd lines
|
|
int tab[2][2][2][4] = {
|
|
{ { { 0, 0, 0, 1 }, // stem - down - even - lines
|
|
{ 0, 0, 0, 2 } // stem - down - odd - lines
|
|
},
|
|
{ { 0, 0, 0, -1 }, // stem - up - even - lines
|
|
{ 0, 0, 0, -2 } // stem - up - odd - lines
|
|
}
|
|
},
|
|
{ { { 0, 0, 1, 2 }, // hook - down - even - lines
|
|
{ 0, 0, 1, 2 } // hook - down - odd - lines
|
|
},
|
|
{ { 0, 0, -1, -2 }, // hook - up - even - lines
|
|
{ 0, 0, -1, -2 } // hook - up - odd - lines
|
|
}
|
|
}
|
|
};
|
|
int odd = (up() ? upLine() : downLine()) & 1;
|
|
int n = tab[_hook ? 1 : 0][up() ? 1 : 0][odd][_tremolo->lines()-1];
|
|
stemLen += n * .5;
|
|
}
|
|
_stem->setLen(stemLen * _spatium);
|
|
if (_hook) {
|
|
_hook->setPos(_stem->hookPos());
|
|
_hook->adjustReadPos();
|
|
}
|
|
if (_stemSlash)
|
|
_stemSlash->layout();
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// process tremolo
|
|
//-----------------------------------------
|
|
|
|
if (_tremolo)
|
|
_tremolo->layout();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layout2
|
|
// Called after horizontal positions of all elements
|
|
// are fixed.
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::layout2()
|
|
{
|
|
if (glissando())
|
|
glissando()->layout();
|
|
qreal _spatium = spatium();
|
|
|
|
//
|
|
// Experimental:
|
|
// look for colliding ledger lines
|
|
//
|
|
|
|
const qreal minDist = _spatium * .17;
|
|
|
|
Segment* s = segment()->prev(Segment::SegChordRestGrace);
|
|
if (s) {
|
|
int strack = staff2track(staffIdx());
|
|
int etrack = strack + VOICES;
|
|
|
|
for (LedgerLine* h = _ledgerLines; h; h = h->next()) {
|
|
Spatium len(h->len());
|
|
qreal y = h->y();
|
|
qreal x = h->x();
|
|
bool found = false;
|
|
qreal cx = h->measureXPos();
|
|
|
|
for (int track = strack; track < etrack; ++track) {
|
|
Chord* e = static_cast<Chord*>(s->element(track));
|
|
if (!e || e->type() != CHORD)
|
|
continue;
|
|
for (LedgerLine* ll = e->ledgerLines(); ll; ll = ll->next()) {
|
|
if (ll->y() != y)
|
|
continue;
|
|
|
|
qreal d = cx - ll->measureXPos() - (ll->len().val() * _spatium);
|
|
if (d < minDist) {
|
|
//
|
|
// the ledger lines overlap
|
|
//
|
|
qreal shorten = (minDist - d) * .5;
|
|
x += shorten;
|
|
len -= Spatium(shorten / _spatium);
|
|
ll->setLen(ll->len() - Spatium(shorten / _spatium));
|
|
h->setLen(len);
|
|
h->setPos(x, y);
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
if (found)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layout
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::layout()
|
|
{
|
|
if (_notes.empty())
|
|
return;
|
|
|
|
qreal _spatium = spatium();
|
|
|
|
while (_ledgerLines) {
|
|
LedgerLine* l = _ledgerLines->next();
|
|
delete _ledgerLines;
|
|
_ledgerLines = l;
|
|
}
|
|
|
|
qreal lx = 0.0;
|
|
Note* upnote = upNote();
|
|
qreal headWidth = symbols[score()->symIdx()][quartheadSym].width(magS());
|
|
|
|
qreal minNoteDistance = score()->styleS(ST_minNoteDistance).val() * _spatium;
|
|
bool useTab = false;
|
|
StaffTypeTablature* tab = 0;
|
|
|
|
if (staff() && staff()->isTabStaff()) {
|
|
//
|
|
// TABLATURE STAVES
|
|
//
|
|
useTab = true;
|
|
tab = (StaffTypeTablature*)staff()->staffType();
|
|
qreal lineDist = tab->lineDistance().val();
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
Note* note = _notes.at(i);
|
|
note->layout();
|
|
note->setPos(0.0, _spatium * tab->physStringToVisual(note->string()) * lineDist);
|
|
note->layout2();
|
|
}
|
|
// if tab type is stemless or duration longer than half (if halves have stems) or duration longer than crochet
|
|
// remove stems
|
|
if (tab->slashStyle() || durationType().type() <
|
|
(tab->minimStyle() != TAB_MINIM_NONE ? TDuration::V_HALF : TDuration::V_QUARTER) ) {
|
|
delete _stem;
|
|
delete _hook;
|
|
_stem = 0;
|
|
_hook = 0;
|
|
}
|
|
// if stem is required but missing, add it
|
|
// stem position and length are set in Chord:layoutStem()
|
|
else if (_stem == 0)
|
|
setStem(new Stem(score()));
|
|
// unconditionally delete grace slashes
|
|
delete _stemSlash;
|
|
_stemSlash = 0;
|
|
|
|
if (!tab->genDurations() // if tab is not set for duration symbols
|
|
|| (track2voice(track()))) { // or not in first voice
|
|
//
|
|
// no tab duration symbols
|
|
//
|
|
delete _tabDur; // delete an existing duration symbol
|
|
_tabDur = 0;
|
|
}
|
|
else {
|
|
//
|
|
// tab duration symbols
|
|
//
|
|
// check duration of prev. CR segm
|
|
ChordRest * prevCR = prevChordRest(this);
|
|
// if no previous CR
|
|
// OR duration type and/or number of dots is different from current CR
|
|
// OR previous CR is a rest
|
|
// set a duration symbol (trying to re-use existing symbols where existing to minimize
|
|
// symbol creation and deletion)
|
|
if (prevCR == 0 || prevCR->durationType().type() != durationType().type()
|
|
|| prevCR->dots() != dots()
|
|
|| prevCR->type() == REST) {
|
|
// symbol needed; if not exist, create; if exists, update duration
|
|
if (!_tabDur)
|
|
_tabDur = new TabDurationSymbol(score(), tab, durationType().type(), dots());
|
|
else
|
|
_tabDur->setDuration(durationType().type(), dots(), tab);
|
|
_tabDur->setParent(this);
|
|
_tabDur->layout();
|
|
}
|
|
else { // symbol not needed: if exists, delete
|
|
delete _tabDur;
|
|
_tabDur = 0;
|
|
}
|
|
} // end of if(duration_symbols)
|
|
} // end of if(isTabStaff)
|
|
else {
|
|
//
|
|
// NON-TABLATURE STAVES
|
|
//
|
|
delete _tabDur; // no TAB? no duration symbol! (may happen when converting a TAB into PITCHED)
|
|
_tabDur = 0;
|
|
|
|
if (!segment()) {
|
|
//
|
|
// hack for use in palette
|
|
//
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; i++) {
|
|
Note* note = _notes.at(i);
|
|
note->layout();
|
|
qreal x = 0.0;
|
|
qreal y = note->line() * _spatium * .5;
|
|
note->setPos(x, y);
|
|
}
|
|
computeUp();
|
|
layoutStem();
|
|
return;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// process notes
|
|
// - position
|
|
//-----------------------------------------
|
|
|
|
qreal stepDistance = _spatium * .5;
|
|
int stepOffset = staff()->staffType()->stepOffset();
|
|
|
|
adjustReadPos();
|
|
|
|
qreal stemWidth5;
|
|
qreal noteWidth = _notes.size() ? _notes.at(0)->headWidth() :
|
|
symbols[score()->symIdx()][quartheadSym].width(magS());
|
|
qreal stemX = _up ? noteWidth : 0.0;
|
|
if (stem()) {
|
|
stemWidth5 = stem()->lineWidth() * .5;
|
|
_stem->rxpos() = _up ? stemX - stemWidth5 : stemWidth5;
|
|
}
|
|
else
|
|
stemWidth5 = 0.0;
|
|
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
Note* note = _notes.at(i);
|
|
note->layout();
|
|
qreal x;
|
|
|
|
qreal hw = note->headWidth();
|
|
|
|
if (note->mirror())
|
|
if (_up)
|
|
x = stemX - stemWidth5 * 2;
|
|
else
|
|
x = stemX - hw + stemWidth5 * 2;
|
|
else {
|
|
if (_up)
|
|
x = stemX - hw;
|
|
else
|
|
x = 0.0;
|
|
}
|
|
|
|
note->rypos() = (note->line() + stepOffset) * stepDistance;
|
|
note->rxpos() = x;
|
|
|
|
Accidental* accidental = note->accidental();
|
|
if (accidental)
|
|
x = accidental->x() + x - minNoteDistance;
|
|
if (x < lx)
|
|
lx = x;
|
|
}
|
|
if (stem())
|
|
stem()->rypos() = (_up ? _notes.front() : _notes.back())->rypos();
|
|
|
|
addLedgerLines(stemX, staffMove());
|
|
|
|
for (LedgerLine* ll = _ledgerLines; ll; ll = ll->next())
|
|
ll->layout();
|
|
}
|
|
|
|
//
|
|
// COMMON TO ALL STAVES
|
|
//
|
|
qreal lll = -lx;
|
|
|
|
if (_arpeggio) {
|
|
qreal headHeight = upnote->headHeight();
|
|
_arpeggio->layout();
|
|
lll += _arpeggio->width() + _spatium * .5;
|
|
qreal y = upNote()->pos().y() - headHeight * .5;
|
|
qreal h = downNote()->pos().y() - y;
|
|
_arpeggio->setHeight(h);
|
|
_arpeggio->setPos(-lll, y);
|
|
|
|
// handle the special case of _arpeggio->span() > 1
|
|
// in layoutArpeggio2() after page layout has done so we
|
|
// know the y position of the next staves
|
|
}
|
|
|
|
if (_glissando)
|
|
lll += _spatium * .5;
|
|
|
|
qreal rrr = 0.0;
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
Note* note = _notes.at(i);
|
|
qreal lhw = useTab ? note->tabHeadWidth(tab) : note->headWidth();
|
|
qreal rr = 0.0;
|
|
if (note->mirror()) {
|
|
if (up())
|
|
rr = lhw * 2.0;
|
|
else {
|
|
if (lhw > lll)
|
|
lll = lhw;
|
|
}
|
|
}
|
|
else
|
|
rr = lhw;
|
|
if (rr > rrr)
|
|
rrr = rr;
|
|
qreal xx = note->pos().x() + headWidth + pos().x();
|
|
if (xx > dotPosX())
|
|
setDotPosX(xx);
|
|
}
|
|
if (dots()) {
|
|
qreal x = dotPosX() + point(score()->styleS(ST_dotNoteDistance)
|
|
+ (dots()-1) * score()->styleS(ST_dotDotDistance));
|
|
x += symbols[score()->symIdx()][dotSym].width(1.0);
|
|
if (x > rrr)
|
|
rrr = x;
|
|
}
|
|
|
|
if (_hook) {
|
|
_hook->layout();
|
|
if (up() && !useTab)
|
|
rrr += _hook->width() + minNoteDistance;
|
|
}
|
|
|
|
if (_noteType != NOTE_NORMAL) {
|
|
// qreal m = score()->styleD(ST_graceNoteMag);
|
|
static const qreal m = .9;
|
|
lll *= m;
|
|
rrr *= m;
|
|
}
|
|
|
|
_space.setLw(lll);
|
|
_space.setRw(rrr + ipos().x());
|
|
|
|
n = _el.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
Element* e = _el.at(i);
|
|
e->layout();
|
|
if (e->type() == CHORDLINE) {
|
|
int x = bbox().translated(e->pos()).right();
|
|
if (x > _space.rw())
|
|
_space.setRw(x);
|
|
}
|
|
}
|
|
// bbox();
|
|
|
|
QRectF bb;
|
|
n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
Note* note = _notes.at(i);
|
|
note->layout2();
|
|
bb |= note->bbox().translated(note->pos());
|
|
}
|
|
for (LedgerLine* ll = _ledgerLines; ll; ll = ll->next())
|
|
bb |= ll->bbox().translated(ll->pos());
|
|
n = _articulations.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
Articulation* a = _articulations.at(i);
|
|
bb |= a->bbox().translated(a->pos());
|
|
}
|
|
if (_hook)
|
|
bb |= _hook->bbox().translated(_hook->pos());
|
|
if (_stem)
|
|
bb |= _stem->bbox().translated(_stem->pos());
|
|
if (_arpeggio)
|
|
bb |= _arpeggio->bbox().translated(_arpeggio->pos());
|
|
if (_glissando)
|
|
bb |= _glissando->bbox().translated(_glissando->pos());
|
|
if (_stemSlash)
|
|
bb |= _stemSlash->bbox().translated(_stemSlash->pos());
|
|
if (_tremolo)
|
|
bb |= _tremolo->bbox().translated(_tremolo->pos());
|
|
if (staff() && staff()->isTabStaff() && _tabDur)
|
|
bb |= _tabDur->bbox().translated(_tabDur->pos());
|
|
setbbox(bb);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layoutArpeggio2
|
|
// called after layout of page
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::layoutArpeggio2()
|
|
{
|
|
if (!_arpeggio)
|
|
return;
|
|
Note* upnote = upNote();
|
|
qreal headHeight = upnote->headHeight();
|
|
qreal y = upNote()->pagePos().y() - headHeight * .5;
|
|
int span = _arpeggio->span();
|
|
Note* dnote = downNote();
|
|
int btrack = track() + (span - 1) * VOICES;
|
|
ChordRest* bchord = static_cast<ChordRest*>(segment()->element(btrack));
|
|
|
|
if (bchord && bchord->type() == CHORD)
|
|
dnote = static_cast<Chord*>(bchord)->downNote();
|
|
qreal h = dnote->pagePos().y() - y;
|
|
_arpeggio->setHeight(h);
|
|
|
|
QList<Note*> notes;
|
|
int n = _notes.size();
|
|
for (int j = n - 1; j >= 0; --j) {
|
|
Note* note = _notes[j];
|
|
if (note->tieBack())
|
|
continue;
|
|
notes.prepend(note);
|
|
}
|
|
|
|
for (int i = 1; i < span; ++i) {
|
|
ChordRest* c = static_cast<ChordRest*>(segment()->element(track() + i * VOICES));
|
|
if (c && c->type() == CHORD) {
|
|
QList<Note*> nl = static_cast<Chord*>(c)->notes();
|
|
int n = nl.size();
|
|
for (int j = n - 1; j >= 0; --j) {
|
|
Note* note = nl[j];
|
|
if (note->tieBack())
|
|
continue;
|
|
notes.prepend(note);
|
|
}
|
|
}
|
|
}
|
|
// bool up = _arpeggio->subtype() != ARP_DOWN;
|
|
// if (!notes.isEmpty())
|
|
// renderArpeggio(notes, up);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// findNote
|
|
//---------------------------------------------------------
|
|
|
|
Note* Chord::findNote(int pitch) const
|
|
{
|
|
int n = _notes.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
Note* n = _notes.at(i);
|
|
if (n->pitch() == pitch)
|
|
return n;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// noteLessThan
|
|
//---------------------------------------------------------
|
|
|
|
static bool noteLessThan(const Note* n1, const Note* n2)
|
|
{
|
|
return n1->pitch() <= n2->pitch();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// pitchChanged
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::pitchChanged()
|
|
{
|
|
qSort(_notes.begin(), _notes.end(), noteLessThan);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// drop
|
|
//---------------------------------------------------------
|
|
|
|
Element* Chord::drop(const DropData& data)
|
|
{
|
|
Element* e = data.element;
|
|
switch (e->type()) {
|
|
case ARTICULATION:
|
|
{
|
|
Articulation* atr = static_cast<Articulation*>(e);
|
|
Articulation* oa = hasArticulation(atr);
|
|
if (oa) {
|
|
delete atr;
|
|
atr = 0;
|
|
// if attribute is already there, remove
|
|
// score()->cmdRemove(oa); // unexpected behaviour?
|
|
score()->select(oa, SELECT_SINGLE, 0);
|
|
}
|
|
else {
|
|
atr->setParent(this);
|
|
atr->setTrack(track());
|
|
score()->undoAddElement(atr);
|
|
}
|
|
return atr;
|
|
}
|
|
|
|
case CHORDLINE:
|
|
e->setParent(this);
|
|
score()->undoAddElement(e);
|
|
break;
|
|
|
|
case TREMOLO:
|
|
{
|
|
Tremolo* t = static_cast<Tremolo*>(e);
|
|
if (t->twoNotes()) {
|
|
Segment* s = segment()->next();
|
|
while (s) {
|
|
if (s->element(track()) && s->element(track())->type() == CHORD)
|
|
break;
|
|
s = s->next();
|
|
}
|
|
if (s == 0) {
|
|
qDebug("no segment for second note of tremolo found\n");
|
|
delete e;
|
|
return 0;
|
|
}
|
|
Chord* ch2 = static_cast<Chord*>(s->element(track()));
|
|
t->setChords(this, ch2);
|
|
}
|
|
}
|
|
if (tremolo())
|
|
score()->undoRemoveElement(tremolo());
|
|
e->setParent(this);
|
|
e->setTrack(track());
|
|
score()->undoAddElement(e);
|
|
break;
|
|
|
|
case ARPEGGIO:
|
|
{
|
|
Arpeggio* a = static_cast<Arpeggio*>(e);
|
|
a->setParent(this);
|
|
a->setHeight(spatium() * 5); //DEBUG
|
|
score()->undoAddElement(a);
|
|
}
|
|
return e;
|
|
|
|
default:
|
|
return ChordRest::drop(data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// dotPosX
|
|
//---------------------------------------------------------
|
|
|
|
qreal Chord::dotPosX() const
|
|
{
|
|
return segment()->dotPosX(staffIdx());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setDotPosX
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::setDotPosX(qreal val)
|
|
{
|
|
segment()->setDotPosX(staffIdx(), val);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getProperty
|
|
//---------------------------------------------------------
|
|
|
|
QVariant Chord::getProperty(P_ID propertyId) const
|
|
{
|
|
switch(propertyId) {
|
|
case P_NO_STEM: return noStem();
|
|
case P_SMALL: return small();
|
|
case P_STEM_DIRECTION: return int(stemDirection());
|
|
default:
|
|
return ChordRest::getProperty(propertyId);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setProperty
|
|
//---------------------------------------------------------
|
|
|
|
bool Chord::setProperty(P_ID propertyId, const QVariant& v)
|
|
{
|
|
switch(propertyId) {
|
|
case P_NO_STEM:
|
|
setNoStem(v.toBool());
|
|
score()->setLayoutAll(true);
|
|
break;
|
|
case P_SMALL:
|
|
setSmall(v.toBool());
|
|
score()->setLayoutAll(true);
|
|
break;
|
|
case P_STEM_DIRECTION:
|
|
setStemDirection(MScore::Direction(v.toInt()));
|
|
score()->setLayoutAll(true);
|
|
break;
|
|
default:
|
|
return ChordRest::setProperty(propertyId, v);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layoutArticulation
|
|
// called from chord()->layoutArticulations()
|
|
//---------------------------------------------------------
|
|
|
|
QPointF Chord::layoutArticulation(Articulation* a)
|
|
{
|
|
qreal _spatium = spatium();
|
|
|
|
a->layout();
|
|
ArticulationAnchor aa = a->anchor();
|
|
|
|
qreal chordTopY = upPos(); // note position of highest note
|
|
qreal chordBotY = downPos(); // note position of lowest note
|
|
qreal x = centerX();
|
|
qreal y = 0.0;
|
|
|
|
int st = a->subtype();
|
|
|
|
if (st == Articulation_Tenuto || st == Articulation_Staccato) {
|
|
bool bottom;
|
|
if ((aa == A_CHORD) && measure()->hasVoices(a->staffIdx()))
|
|
bottom = !up();
|
|
else
|
|
bottom = (aa == A_BOTTOM_CHORD) || (aa == A_CHORD && up());
|
|
bool stemSide = (bottom != up()) && stem();
|
|
a->setUp(!bottom);
|
|
|
|
QPointF pos;
|
|
if (stem())
|
|
pos = stem()->hookPos();
|
|
|
|
qreal _spatium2 = _spatium * .5;
|
|
if (stemSide) {
|
|
qreal yy = up() ? -_spatium2 : _spatium2;
|
|
int line = lrint((pos.y() + yy) / _spatium);
|
|
if (line >= 0 && line <= 4) // align between staff lines
|
|
pos.ry() = (line * _spatium) + (bottom ? _spatium2 : -_spatium2);
|
|
else {
|
|
qreal dy = (score()->styleS(ST_beamWidth).val() + 1) * _spatium2;
|
|
pos.ry() += bottom ? dy : - dy;
|
|
}
|
|
}
|
|
else {
|
|
int line;
|
|
if (bottom) {
|
|
line = downLine();
|
|
int lines = (staff()->lines() - 1) * 2;
|
|
if (line < lines)
|
|
line = (line & ~1) + 3;
|
|
else
|
|
line += 2;
|
|
pos.rx() -= upNote()->headWidth() * .5;
|
|
}
|
|
else {
|
|
line = upLine();
|
|
if (line > 0)
|
|
line = ((line+1) & ~1) - 3;
|
|
else
|
|
line -= 2;
|
|
pos.rx() += upNote()->headWidth() * .5;
|
|
}
|
|
pos.ry() = line * _spatium2;
|
|
}
|
|
|
|
a->setPos(pos);
|
|
a->adjustReadPos();
|
|
return QPointF(pos);
|
|
}
|
|
|
|
// reserve space for slur
|
|
bool botGap = false;
|
|
bool topGap = false;
|
|
for (Spanner* sp = spannerFor(); sp; sp = sp->next()) {
|
|
if (sp->type() != SLUR)
|
|
continue;
|
|
Slur* s = static_cast<Slur*>(sp);
|
|
if (s->up())
|
|
topGap = true;
|
|
else
|
|
botGap = true;
|
|
}
|
|
for (Spanner* sp = spannerBack(); sp; sp = sp->next()) {
|
|
if (sp->type() != SLUR)
|
|
continue;
|
|
Slur* s = static_cast<Slur*>(sp);
|
|
if (s->up())
|
|
topGap = true;
|
|
else
|
|
botGap = true;
|
|
}
|
|
if (botGap)
|
|
chordBotY += _spatium;
|
|
else
|
|
chordBotY += _spatium * .5;
|
|
if (topGap)
|
|
chordTopY -= _spatium;
|
|
else
|
|
chordTopY -= _spatium * .5;
|
|
|
|
// avoid collisions of staff articulations with chord notes:
|
|
// gap between note and staff articulation is distance0 + 0.5 spatium
|
|
|
|
qreal staffTopY = 0;
|
|
qreal staffBotY = staff()->height();
|
|
|
|
if (stem()) {
|
|
#if 0
|
|
y = stem()->pos().y() + pos().y();
|
|
if (up() && stem()->stemLen() < 0.0)
|
|
y += stem()->stemLen();
|
|
else if (!up() && stem()->stemLen() > 0.0)
|
|
y -= stem()->stemLen();
|
|
#endif
|
|
y = stem()->hookPos().y() + pos().y();
|
|
|
|
if (beam()) {
|
|
qreal bw = score()->styleS(ST_beamWidth).val() * _spatium;
|
|
y += up() ? -bw : bw;
|
|
}
|
|
if (up())
|
|
staffTopY = qMin(staffTopY, y);
|
|
else
|
|
staffBotY = qMax(staffBotY, y);
|
|
}
|
|
|
|
staffTopY = qMin(staffTopY, qreal(chordTopY));
|
|
staffBotY = qMax(staffBotY, qreal(chordBotY));
|
|
|
|
//
|
|
// determine Direction
|
|
//
|
|
if (a->direction() != MScore::AUTO) {
|
|
a->setUp(a->direction() == MScore::UP);
|
|
}
|
|
else {
|
|
if (measure()->hasVoices(a->staffIdx())) {
|
|
a->setUp(up());
|
|
aa = up() ? A_TOP_STAFF : A_BOTTOM_STAFF;
|
|
}
|
|
else {
|
|
if (aa == A_CHORD)
|
|
a->setUp(!up());
|
|
else
|
|
a->setUp(aa == A_TOP_STAFF || aa == A_TOP_CHORD);
|
|
}
|
|
}
|
|
|
|
qreal dist;
|
|
switch(st) {
|
|
case Articulation_Marcato: dist = 1.0 * _spatium; break;
|
|
case Articulation_Sforzatoaccent: dist = 1.5 * _spatium; break;
|
|
default: dist = score()->styleS(ST_propertyDistance).val() * _spatium;
|
|
}
|
|
|
|
if (aa == A_CHORD || aa == A_TOP_CHORD || aa == A_BOTTOM_CHORD) {
|
|
bool bottom;
|
|
if ((aa == A_CHORD) && measure()->hasVoices(a->staffIdx()))
|
|
bottom = !up();
|
|
else
|
|
bottom = (aa == A_BOTTOM_CHORD) || (aa == A_CHORD && up());
|
|
y = bottom ? chordBotY + dist : chordTopY - dist;
|
|
}
|
|
else if (aa == A_TOP_STAFF || aa == A_BOTTOM_STAFF) {
|
|
y = a->up() ? staffTopY - dist : staffBotY + dist;
|
|
}
|
|
a->setPos(x, y);
|
|
a->adjustReadPos();
|
|
return QPointF(x, y);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// reset
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::reset()
|
|
{
|
|
score()->undoChangeProperty(this, P_STEM_DIRECTION, int(MScore::AUTO));
|
|
score()->undoChangeProperty(this, P_BEAM_MODE, int(BEAM_AUTO));
|
|
createPlayEvents(this);
|
|
ChordRest::reset();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setStemSlash
|
|
//---------------------------------------------------------
|
|
|
|
void Chord::setStemSlash(StemSlash* s)
|
|
{
|
|
delete _stemSlash;
|
|
_stemSlash = s;
|
|
}
|
|
|