MuseScore/libmscore/glissando.cpp

750 lines
30 KiB
C++
Raw Normal View History

2012-05-26 14:26:10 +02:00
//=============================================================================
// 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
//=============================================================================
/* TO DO:
- XML export
NICE-TO-HAVE TODO:
- draggable handles of glissando segments
- re-attachable glissando extrema (with [Shift]+arrows, use SlurSegment::edit()
and SlurSegment::changeAnchor() in slur.cpp as models)
*/
#include "arpeggio.h"
2012-05-26 14:26:10 +02:00
#include "glissando.h"
#include "chord.h"
#include "ledgerline.h"
2012-05-26 14:26:10 +02:00
#include "note.h"
#include "notedot.h"
2012-05-26 14:26:10 +02:00
#include "score.h"
#include "segment.h"
#include "staff.h"
#include "system.h"
#include "style.h"
2012-05-26 14:26:10 +02:00
#include "sym.h"
2014-04-09 16:09:21 +02:00
#include "xml.h"
2015-04-03 18:23:51 +02:00
#include "accidental.h"
2012-05-26 14:26:10 +02:00
2013-05-13 18:49:17 +02:00
namespace Ms {
static const qreal GLISS_DEFAULT_LINE_TICKNESS = 0.15;
static const qreal GLISS_PALETTE_WIDTH = 4.0;
static const qreal GLISS_PALETTE_HEIGHT = 4.0;
//---------------------------------------------------------
// GlisandoSegment
//---------------------------------------------------------
//---------------------------------------------------------
// layout
//---------------------------------------------------------
void GlissandoSegment::layout()
{
if (staff())
setMag(staff()->mag());
QRectF r = QRectF(0.0, 0.0, pos2().x(), pos2().y()).normalized();
qreal lw = spatium() * glissando()->lineWidth().val() * .5;
setbbox(r.adjusted(-lw, -lw, lw, lw));
adjustReadPos();
}
//---------------------------------------------------------
// draw
//---------------------------------------------------------
void GlissandoSegment::draw(QPainter* painter) const
{
painter->save();
qreal _spatium = spatium();
QPen pen(glissando()->curColor());
pen.setWidthF(glissando()->lineWidth().val() * spatium());
pen.setCapStyle(Qt::RoundCap);
painter->setPen(pen);
// painter->drawLine(QPointF(), pos2()); // DEBUG
// rotate painter so that the line become horizontal
qreal w = pos2().x();
qreal h = pos2().y();
qreal l = sqrt(w * w + h * h);
qreal wi = asin(-h / l) * 180.0 / M_PI;
2015-05-08 08:57:24 +02:00
qreal scale = painter->worldTransform().m11();
painter->rotate(-wi);
if (glissando()->glissandoType() == Glissando::Type::STRAIGHT) {
painter->drawLine(QLineF(0.0, 0.0, l, 0.0));
}
else if (glissando()->glissandoType() == Glissando::Type::WAVY) {
QRectF b = symBbox(SymId::wiggleTrill);
2015-05-08 08:57:24 +02:00
qreal w = symAdvance(SymId::wiggleTrill);
int n = (int)(l / w); // always round down (truncate) to avoid overlap
qreal x = (l - n*w) * 0.5; // centre line in available space
2016-01-04 14:48:58 +01:00
std::vector<SymId> ids;
2015-05-08 08:57:24 +02:00
for (int i = 0; i < n; ++i)
2016-01-04 14:48:58 +01:00
ids.push_back(SymId::wiggleTrill);
// this is very ugly but fix #68846 for now
bool tmp = MScore::pdfPrinting;
MScore::pdfPrinting = true;
Fixes #19155, #22861 (duplicate of the former) and #23100. __References__: Issues: https://musescore.org/en/node/19155 https://musescore.org/en/node/22861 https://musescore.org/en/node/23100 __Description__: Allows to change the start and end note to which a glissando is anchored after it has been entered. Either anchor can be changed independently. The user interface follows the current working of other 'snappable' lines. Once either the start or end grip is selected: - `[Shift]+[Left]` snaps the anchor to the previous chord, defaulting to its top note. - `[Shift]+[Right]` snaps to the next chord, defaulting to its top note. - `[Shift]+[Up]` snaps to the note above (possibly in a chord, voice or staff above the current one). - `[Shift]+[Down]` snaps to the note below (possibly in a chord, voice or staff below the current one). This permits to set the anchor points of a glissando to any note in the score, allowing several glissandi between the notes of the same two chords and other complex configurations (glissandi skipping intermediate chords, start and end notes in different voices or staves, and so on). It is possible to move the anchor to a different staff of the same instrument, but not to a different instrument; also, it is not possible to 'cross' a change of instrument in the same staff. __Known limitations__: - The `[Shift]+[Up]` and `[Shift]+[Down]` use the same note-finding functions as the `[Alt]+[Up]` and `[Alt]+[Down]`actions which move the selection cursor to the above and below note, even across voices or staves. Occasionally, in particular if the note immediately above or below is not time-aligned, the algorithm has little expected results; however, the behaviour is already known to the user. Improving the algorithm would benefit both uses. __Notes__: - Most of the added infrastructure is not specific to glissando but to any spanner anchored to notes, then it should also add after-the-fact "snap to" note support to note-anchored text line. - When moving an anchor, the algorithm usually prefers a note in the same voice/staff of the old note if it exists; if there is none, it tries other voices of the same staff. - The change of anchor is undoable. - The fix corrects the management of the `Chord::_endsGlissando` flag, taking into account that a chord can be the ending point of several glissandi and removing one of them not necessarily means the chord no longer ends a glissando (another glissando may still exists). - The fix also improved the rendering of the glissando wavy line, with better alignment with anchor notes and, with glissando text, better text-line spacing.
2015-08-06 11:11:16 +02:00
score()->scoreFont()->draw(ids, painter, magS(), QPointF(x, -(b.y() + b.height()*0.5) ), scale /**2.0*/);
MScore::pdfPrinting = tmp;
}
if (glissando()->showText()) {
const TextStyle& st = score()->textStyle(TextStyleType::GLISSANDO);
2016-09-04 16:17:33 +02:00
QRectF r = st.fontMetrics(_spatium).boundingRect(glissando()->text());
// if text longer than available space, skip it
if (r.width() < l) {
qreal yOffset = r.height() + r.y(); // find text descender height
// raise text slightly above line and slightly more with WAVY than with STRAIGHT
Fixes #19155, #22861 (duplicate of the former) and #23100. __References__: Issues: https://musescore.org/en/node/19155 https://musescore.org/en/node/22861 https://musescore.org/en/node/23100 __Description__: Allows to change the start and end note to which a glissando is anchored after it has been entered. Either anchor can be changed independently. The user interface follows the current working of other 'snappable' lines. Once either the start or end grip is selected: - `[Shift]+[Left]` snaps the anchor to the previous chord, defaulting to its top note. - `[Shift]+[Right]` snaps to the next chord, defaulting to its top note. - `[Shift]+[Up]` snaps to the note above (possibly in a chord, voice or staff above the current one). - `[Shift]+[Down]` snaps to the note below (possibly in a chord, voice or staff below the current one). This permits to set the anchor points of a glissando to any note in the score, allowing several glissandi between the notes of the same two chords and other complex configurations (glissandi skipping intermediate chords, start and end notes in different voices or staves, and so on). It is possible to move the anchor to a different staff of the same instrument, but not to a different instrument; also, it is not possible to 'cross' a change of instrument in the same staff. __Known limitations__: - The `[Shift]+[Up]` and `[Shift]+[Down]` use the same note-finding functions as the `[Alt]+[Up]` and `[Alt]+[Down]`actions which move the selection cursor to the above and below note, even across voices or staves. Occasionally, in particular if the note immediately above or below is not time-aligned, the algorithm has little expected results; however, the behaviour is already known to the user. Improving the algorithm would benefit both uses. __Notes__: - Most of the added infrastructure is not specific to glissando but to any spanner anchored to notes, then it should also add after-the-fact "snap to" note support to note-anchored text line. - When moving an anchor, the algorithm usually prefers a note in the same voice/staff of the old note if it exists; if there is none, it tries other voices of the same staff. - The change of anchor is undoable. - The fix corrects the management of the `Chord::_endsGlissando` flag, taking into account that a chord can be the ending point of several glissandi and removing one of them not necessarily means the chord no longer ends a glissando (another glissando may still exists). - The fix also improved the rendering of the glissando wavy line, with better alignment with anchor notes and, with glissando text, better text-line spacing.
2015-08-06 11:11:16 +02:00
yOffset += _spatium * (glissando()->glissandoType() == Glissando::Type::WAVY ? 0.4 : 0.1);
2016-09-04 16:17:33 +02:00
QFont f = st.font(_spatium);
f.setPointSizeF(f.pointSizeF() * MScore::pixelRatio);
painter->setFont(f);
qreal x = (l - r.width()) * 0.5;
painter->drawText(QPointF(x, -yOffset), glissando()->text());
}
}
painter->restore();
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// getProperty
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
QVariant GlissandoSegment::getProperty(P_ID id) const
{
switch (id) {
// route properties of the whole Glissando element to it
case P_ID::GLISS_TYPE:
case P_ID::GLISS_TEXT:
case P_ID::GLISS_SHOW_TEXT:
case P_ID::GLISSANDO_STYLE:
case P_ID::PLAY:
return glissando()->getProperty(id);
default:
return LineSegment::getProperty(id);
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool GlissandoSegment::setProperty(P_ID id, const QVariant& v)
{
switch (id) {
case P_ID::GLISS_TYPE:
case P_ID::GLISS_TEXT:
case P_ID::GLISS_SHOW_TEXT:
case P_ID::GLISSANDO_STYLE:
case P_ID::PLAY:
return glissando()->setProperty(id, v);
default:
return LineSegment::setProperty(id, v);
}
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant GlissandoSegment::propertyDefault(P_ID id) const
{
switch (id) {
case P_ID::GLISS_TYPE:
case P_ID::GLISS_TEXT:
case P_ID::GLISS_SHOW_TEXT:
case P_ID::GLISSANDO_STYLE:
case P_ID::PLAY:
return glissando()->propertyDefault(id);
default:
return LineSegment::propertyDefault(id);
}
}
//=========================================================
// Glissando
//=========================================================
2012-05-26 14:26:10 +02:00
Glissando::Glissando(Score* s)
: SLine(s)
2012-05-26 14:26:10 +02:00
{
setFlags(ElementFlag::MOVABLE | ElementFlag::SELECTABLE);
2013-02-28 15:06:54 +01:00
_glissandoType = Type::STRAIGHT;
2013-02-28 15:06:54 +01:00
_text = "gliss.";
_showText = true;
setDiagonal(true);
setGlissandoStyle(MScore::GlissandoStyle::CHROMATIC);
setPlayGlissando(true);
setLineWidth(Spatium(GLISS_DEFAULT_LINE_TICKNESS));
setAnchor(Spanner::Anchor::NOTE);
2012-05-26 14:26:10 +02:00
}
2013-02-28 15:06:54 +01:00
Glissando::Glissando(const Glissando& g)
: SLine(g)
2013-02-28 15:06:54 +01:00
{
_glissandoType = g._glissandoType;
_glissandoStyle = g._glissandoStyle;
_playGlissando = g._playGlissando;
_text = g._text;
_showText = g._showText;
2013-02-28 15:06:54 +01:00
}
//---------------------------------------------------------
// createLineSegment
//---------------------------------------------------------
LineSegment* Glissando::createLineSegment()
{
GlissandoSegment* seg = new GlissandoSegment(score());
seg->setFlag(ElementFlag::ON_STAFF, false);
seg->setTrack(track());
seg->setColor(color());
return seg;
}
//---------------------------------------------------------
// scanElements
//---------------------------------------------------------
void Glissando::scanElements(void* data, void (*func)(void*, Element*), bool all)
{
func(data, this);
// don't scan segments belonging to systems; the systems themselves will scan them
for (SpannerSegment* seg : segments)
if (!seg->parent() || seg->parent()->type() != Element::Type::SYSTEM)
seg->scanElements(data, func, all);
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// layout
//---------------------------------------------------------
void Glissando::layout()
{
qreal _spatium = spatium();
if (score() == gscore || !startElement() || !endElement()) { // for use in palettes or while dragging
2016-02-06 22:03:43 +01:00
if (spannerSegments().empty())
add(createLineSegment());
LineSegment* s = frontSegment();
s->setPos(QPointF());
s->setPos2(QPointF(_spatium * GLISS_PALETTE_WIDTH, -_spatium * GLISS_PALETTE_HEIGHT));
s->layout();
2012-05-26 14:26:10 +02:00
return;
}
SLine::layout();
2012-05-26 14:26:10 +02:00
setPos(0.0, 0.0);
adjustReadPos();
2012-05-26 14:26:10 +02:00
Note* anchor1 = static_cast<Note*>(startElement());
Note* anchor2 = static_cast<Note*>(endElement());
Chord* cr1 = anchor1->chord();
Chord* cr2 = anchor2->chord();
GlissandoSegment* segm1 = static_cast<GlissandoSegment*>(frontSegment());
GlissandoSegment* segm2 = static_cast<GlissandoSegment*>(backSegment());
// Note: line segments are defined by
// initial point: ipos() (relative to system origin)
// ending point: pos2() (relative to initial point)
// LINE ENDING POINTS TO NOTEHEAD CENTRES
// assume gliss. line goes from centre of initial note centre to centre of ending note:
// move first segment origin and last segment ending point from notehead origin to notehead centre
QPointF offs1 = QPointF(anchor1->headWidth() * 0.5, 0.0);
QPointF offs2 = QPointF(anchor2->headWidth() * 0.5, 0.0);
// AVOID HORIZONTAL LINES
int upDown = (0 < (anchor2->pitch() - anchor1->pitch())) - ((anchor2->pitch() - anchor1->pitch()) < 0);
// on TAB's, glissando are by necessity on the same string, this gives an horizontal glissando line;
// make bottom end point lower and top ending point higher
if (cr1->staff()->isTabStaff()) {
2015-03-06 13:38:22 +01:00
qreal yOff = cr1->staff()->lineDistance() * 0.4 * _spatium;
offs1.ry() += yOff * upDown;
offs2.ry() -= yOff * upDown;
}
// if not TAB, angle glissando between notes on the same line
else {
if (anchor1->line() == anchor2->line()) {
offs1.ry() += _spatium * 0.25 * upDown;
offs2.ry() -= _spatium * 0.25 * upDown;
2013-09-24 21:34:15 +02:00
}
}
// move initial point of first segment and adjust its length accordingly
segm1->setPos (segm1->ipos() + offs1);
segm1->setPos2(segm1->ipos2() - offs1);
// adjust ending point of last segment
segm2->setPos2(segm2->ipos2() + offs2);
// FINAL SYSTEM-INITIAL NOTE
// if the last gliss. segment attaches to a system-initial note, some extra width has to be added
if (cr2->segment()->measure() == cr2->segment()->system()->firstMeasure() && cr2->rtick() == 0
// but ignore graces after, as they are not the first note of the system,
// even if their segment is the first segment of the system
&& !(cr2->noteType() == NoteType::GRACE8_AFTER
|| cr2->noteType() == NoteType::GRACE16_AFTER || cr2->noteType() == NoteType::GRACE32_AFTER)
// also ignore if cr1 is a child of cr2, which means cr1 is a grace-before of cr2
&& !(cr1->parent() == cr2))
{
segm2->rxpos() -= GLISS_STARTOFSYSTEM_WIDTH * _spatium;
segm2->rxpos2()+= GLISS_STARTOFSYSTEM_WIDTH * _spatium;
}
// INTERPOLATION OF INTERMEDIATE POINTS
// This probably belongs to SLine class itself; currently it does not seem
// to be needed for anything else than Glissando, though
// get total x-width and total y-height of all segments
qreal xTot = 0.0;
for (SpannerSegment* segm : spannerSegments())
xTot += segm->ipos2().x();
qreal y0 = segm1->ipos().y();
qreal yTot = segm2->ipos().y() + segm2->ipos2().y() - y0;
qreal ratio = yTot / xTot;
// interpolate y-coord of intermediate points across total width and height
qreal xCurr = 0.0;
qreal yCurr;
for (int i = 0; i < spannerSegments().count()-1; i++) {
SpannerSegment* segm = segmentAt(i);
xCurr += segm->ipos2().x();
yCurr = y0 + ratio * xCurr;
segm->rypos2() = yCurr - segm->ipos().y(); // position segm. end point at yCurr
// next segment shall start where this segment stopped
segm = segmentAt(i+1);
segm->rypos2() += segm->ipos().y() - yCurr; // adjust next segm. vertical length
segm->rypos() = yCurr; // position next segm. start point at yCurr
}
2012-05-26 14:26:10 +02:00
// STAY CLEAR OF NOTE APPENDAGES
2012-05-26 14:26:10 +02:00
// initial note dots / ledger line / notehead
offs1 *= -1.0; // discount changes already applied
int dots = cr1->dots();
LedgerLine * ledLin = cr1->ledgerLines();
// if dots, start at right of last dot
// if no dots, from right of ledger line, if any; from right of notehead, if no ledger line
offs1.rx() += (dots && anchor1->dot(dots-1) ? anchor1->dot(dots-1)->pos().x() + anchor1->dot(dots-1)->width()
: (ledLin ? ledLin->pos().x() + ledLin->width() : anchor1->headWidth()) );
// final note arpeggio / accidental / ledger line / accidental / arpeggio (i.e. from outermost to innermost)
offs2 *= -1.0; // discount changes already applied
if (Arpeggio* a = cr2->arpeggio())
offs2.rx() += a->pos().x() + a->userOff().x();
else if (Accidental* a = anchor2->accidental())
offs2.rx() += a->pos().x() + a->userOff().x();
else if ( (ledLin = cr2->ledgerLines()) != nullptr)
offs2.rx() += ledLin->pos().x();
// add another a quarter spatium of 'air'
offs1.rx() += _spatium * 0.25;
offs2.rx() -= _spatium * 0.25;
// apply offsets: shorten first segment by x1 (and proportionally y) and adjust its length accordingly
offs1.ry() = segm1->ipos2().y() * offs1.x() / segm1->ipos2().x();
segm1->setPos(segm1->ipos() + offs1);
segm1->setPos2(segm1->ipos2() - offs1);
// adjust last segment length by x2 (and proportionally y)
offs2.ry() = segm2->ipos2().y() * offs2.x() / segm2->ipos2().x();
segm2->setPos2(segm2->ipos2() + offs2);
for (SpannerSegment* segm : spannerSegments())
static_cast<GlissandoSegment*>(segm)->layout();
// compute glissando bbox as the bbox of the last segment, relative to the end anchor note
QPointF anchor2PagePos = anchor2->pagePos();
QPointF system2PagePos = cr2->segment()->system()->pagePos();
QPointF anchor2SystPos = anchor2PagePos - system2PagePos;
QRectF r = QRectF(anchor2SystPos - segm2->pos(), anchor2SystPos - segm2->pos() - segm2->pos2()).normalized();
qreal lw = _spatium * lineWidth().val() * .5;
2012-05-26 14:26:10 +02:00
setbbox(r.adjusted(-lw, -lw, lw, lw));
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void Glissando::write(Xml& xml) const
{
2014-08-15 17:20:20 +02:00
if (!xml.canWrite(this))
return;
xml.stag(QString("%1 id=\"%2\"").arg(name()).arg(xml.spannerId(this)));
2012-05-26 14:26:10 +02:00
if (_showText && !_text.isEmpty())
xml.tag("text", _text);
xml.tag("subtype", int(_glissandoType));
writeProperty(xml, P_ID::PLAY);
writeProperty(xml, P_ID::GLISSANDO_STYLE);
SLine::writeProperties(xml);
2012-05-26 14:26:10 +02:00
xml.etag();
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
2013-01-11 18:10:18 +01:00
void Glissando::read(XmlReader& e)
2012-05-26 14:26:10 +02:00
{
qDeleteAll(spannerSegments());
spannerSegments().clear();
e.addSpanner(e.intAttribute("id", -1), this);
2012-05-26 14:26:10 +02:00
_showText = false;
2013-01-11 18:10:18 +01:00
while (e.readNextStartElement()) {
const QStringRef& tag = e.name();
if (tag == "text") {
2012-05-26 14:26:10 +02:00
_showText = true;
2013-01-11 18:10:18 +01:00
_text = e.readElementText();
2012-05-26 14:26:10 +02:00
}
2013-01-11 18:10:18 +01:00
else if (tag == "subtype")
_glissandoType = Type(e.readInt());
else if (tag == "glissandoStyle") {
setProperty(P_ID::GLISSANDO_STYLE, Ms::getProperty(P_ID::GLISSANDO_STYLE, e));
} else if ( tag == "play") {
setPlayGlissando(e.readBool());
}
else if (!SLine::readProperties(e))
2013-01-11 18:10:18 +01:00
e.unknown();
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// computeStartElement
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
/*
void Glissando::computeStartElement()
2012-05-26 14:26:10 +02:00
{
// if there is already a start note, done.
if (_startElement != nullptr && _startElement->type() == Element::Type::NOTE)
return;
// if neither a start note or an end note, we got a problem!
if (_endElement == nullptr || _endElement->type() != Element::Type::NOTE) {
// TODO: no start, no end: we probably should delete this glissando or just abort() ?
return;
}
2012-05-26 14:26:10 +02:00
int trk = track();
Part* part = _endElement->part();
Segment* segm = static_cast<Note*>(_endElement)->chord()->segment();
if (segm != nullptr)
segm = segm->prev1();
while (segm) {
// if previous segment is a ChordRest segment
if (segm->segmentType() == Segment::Type::ChordRest) {
// look for a Chord in the same track and get its top note, if found
if (segm->element(trk) && segm->element(trk)->type() == Element::Type::CHORD) {
_startElement = static_cast<Chord*>(segm->element(trk))->upNote();
_startElement->add(this);
return;
}
// if no chord, look for other chords in the same instrument
for (Element* currChord : segm->elist())
if (currChord != nullptr && currChord->type() == Element::Type::CHORD
&& static_cast<Chord*>(currChord)->part() == part) {
_startElement = static_cast<Chord*>(currChord->upNote();
_startElement->add(this);
return;
}
}
segm = segm->prev1();
}
// we have a problem! delete this glissando? abort()?
qDebug("no first note for glissando found");
}
*/
2013-02-28 15:06:54 +01:00
//---------------------------------------------------------
// undoSetGlissandoType
2013-02-28 15:06:54 +01:00
//---------------------------------------------------------
void Glissando::undoSetGlissandoType(Type t)
2013-02-28 15:06:54 +01:00
{
2016-06-09 09:26:13 +02:00
undoChangeProperty(P_ID::GLISS_TYPE, int(t));
2013-02-28 15:06:54 +01:00
}
//---------------------------------------------------------
// undoSetText
//---------------------------------------------------------
void Glissando::undoSetText(const QString& s)
{
2016-06-09 09:26:13 +02:00
undoChangeProperty(P_ID::GLISS_TEXT, s);
2013-02-28 15:06:54 +01:00
}
//---------------------------------------------------------
// undoSetShowText
//---------------------------------------------------------
void Glissando::undoSetShowText(bool f)
{
2016-06-09 09:26:13 +02:00
undoChangeProperty(P_ID::GLISS_SHOW_TEXT, f);
2013-02-28 15:06:54 +01:00
}
//---------------------------------------------------------
// STATIC FUNCTIONS: guessInitialNote
//
// Used while reading old scores (either 1.x or transitional 2.0) to determine (guess!)
// the glissando initial note from its final chord. Returns the top note of previous chord
// of the same instrument, preferring the chord in the same track as chord, if it exists.
//
// CANNOT be called if the final chord and/or its segment do not exist yet in the score
//
// Parameter: chord: the chord this glissando ends into
// Returns: the top note in a suitable previous chord or nullptr if none found.
//---------------------------------------------------------
Note* Glissando::guessInitialNote(Chord* chord)
{
switch (chord->noteType()) {
2016-02-15 12:23:28 +01:00
// case NoteType::INVALID:
// return nullptr;
// for grace notes before, previous chord is previous chord of parent chord
case NoteType::ACCIACCATURA:
case NoteType::APPOGGIATURA:
case NoteType::GRACE4:
case NoteType::GRACE16:
case NoteType::GRACE32:
// move unto parent chord and proceed to standard case
if (chord->parent() && chord->parent()->type() == Element::Type::CHORD)
chord = static_cast<Chord*>(chord->parent());
else
return nullptr;
break;
// for grace notes after, return top note of parent chord
case NoteType::GRACE8_AFTER:
case NoteType::GRACE16_AFTER:
case NoteType::GRACE32_AFTER:
if (chord->parent() && chord->parent()->type() == Element::Type::CHORD)
return static_cast<Chord*>(chord->parent())->upNote();
else // no parent or parent is not a chord?
return nullptr;
case NoteType::NORMAL:
// if chord has grace notes before, the last one is the previous note
2016-02-06 22:03:43 +01:00
QVector<Chord*>graces = chord->graceNotesBefore();
if (graces.size() > 0)
return graces.last()->upNote();
break; // else process to standard case
}
// standard case (NORMAL or grace before chord)
// if parent not a segment, can't locate a target note
if (chord->parent()->type() != Element::Type::SEGMENT)
return nullptr;
int chordTrack = chord->track();
Segment* segm = chord->segment();
Part* part = chord->part();
if (segm != nullptr)
segm = segm->prev1();
while (segm) {
// if previous segment is a ChordRest segment
if (segm->segmentType() == Segment::Type::ChordRest) {
Chord* target = nullptr;
// look for a Chord in the same track
if (segm->element(chordTrack) && segm->element(chordTrack)->type() == Element::Type::CHORD)
target = static_cast<Chord*>(segm->element(chordTrack));
else // if no same track, look for other chords in the same instrument
for (Element* currChord : segm->elist())
if (currChord != nullptr && currChord->type() == Element::Type::CHORD
&& static_cast<Chord*>(currChord)->part() == part) {
target = static_cast<Chord*>(currChord);
break;
}
// if we found a target previous chord
if (target) {
// if chord has grace notes after, the last one is the previous note
2016-02-06 22:03:43 +01:00
QVector<Chord*>graces = target->graceNotesAfter();
if (graces.size() > 0)
return graces.last()->upNote();
return target->upNote(); // if no grace after, return top note
}
}
segm = segm->prev1();
}
qDebug("no first note for glissando found");
return nullptr;
}
//---------------------------------------------------------
// STATIC FUNCTIONS: guessFinalNote
//
2016-02-15 12:23:28 +01:00
// Used while dropping a glissando on a note to determine (guess!) the glissando final
// note from its initial chord.
// Returns the top note of next chord of the same instrument,
// preferring the chord in the same track as chord, if it exists.
//
// Parameter: chord: the chord this glissando start from
// Returns: the top note in a suitable following chord or nullptr if none found
//---------------------------------------------------------
Note* Glissando::guessFinalNote(Chord* chord)
{
switch (chord->noteType()) {
2016-02-15 12:23:28 +01:00
// case NoteType::INVALID:
// return nullptr;
// for grace notes before, return top note of parent chord
// TODO : if the grace-before is not the LAST ONE, this still returns the main note
// which is probably not correct; however a glissando between two grace notes
// probably makes little sense.
case NoteType::ACCIACCATURA:
case NoteType::APPOGGIATURA:
case NoteType::GRACE4:
case NoteType::GRACE16:
case NoteType::GRACE32:
if (chord->parent() && chord->parent()->type() == Element::Type::CHORD)
return static_cast<Chord*>(chord->parent())->upNote();
else // no parent or parent is not a chord?
return nullptr;
// for grace notes after, next chord is next chord of parent chord
// TODO : same note as case above!
case NoteType::GRACE8_AFTER:
case NoteType::GRACE16_AFTER:
case NoteType::GRACE32_AFTER:
// move unto parent chord and proceed to standard case
if (chord->parent() && chord->parent()->type() == Element::Type::CHORD)
chord = static_cast<Chord*>(chord->parent());
else
return nullptr;
break;
case NoteType::NORMAL:
// if chord has grace notes after, the first one is the next note
2016-02-06 22:03:43 +01:00
QVector<Chord*>graces = chord->graceNotesAfter();
if (graces.size() > 0)
return graces.first()->upNote();
break;
}
// standard case (NORMAL or grace after chord)
// if parent not a segment, can't locate a target note
if (chord->parent()->type() != Element::Type::SEGMENT)
return nullptr;
// look for first ChordRest segment after initial note is elapsed
Segment* segm = chord->score()->tick2rightSegment(chord->tick() + chord->actualTicks());
int chordTrack = chord->track();
Part* part = chord->part();
while (segm) {
// if next segment is a ChordRest segment
if (segm->segmentType() == Segment::Type::ChordRest) {
Chord* target = nullptr;
// look for a Chord in the same track
if (segm->element(chordTrack) && segm->element(chordTrack)->type() == Element::Type::CHORD)
target = static_cast<Chord*>(segm->element(chordTrack));
else // if no same track, look for other chords in the same instrument
for (Element* currChord : segm->elist())
if (currChord != nullptr && currChord->type() == Element::Type::CHORD
&& static_cast<Chord*>(currChord)->part() == part) {
target = static_cast<Chord*>(currChord);
break;
}
// if we found a target next chord
if (target) {
// if chord has grace notes before, the first one is the next note
2016-02-06 22:03:43 +01:00
QVector<Chord*>graces = target->graceNotesBefore();
if (graces.size() > 0)
return graces.first()->upNote();
return target->upNote(); // if no grace before, return top note
}
}
segm = segm->next1();
}
qDebug("no second note for glissando found");
return nullptr;
}
2013-02-28 15:06:54 +01:00
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant Glissando::getProperty(P_ID propertyId) const
{
switch (propertyId) {
2014-05-26 18:18:01 +02:00
case P_ID::GLISS_TYPE:
return int(glissandoType());
2014-05-26 18:18:01 +02:00
case P_ID::GLISS_TEXT:
2013-02-28 15:06:54 +01:00
return text();
2014-05-26 18:18:01 +02:00
case P_ID::GLISS_SHOW_TEXT:
2013-02-28 15:06:54 +01:00
return showText();
case P_ID::GLISSANDO_STYLE:
return int(glissandoStyle());
case P_ID::PLAY:
return bool(playGlissando());
2013-02-28 15:06:54 +01:00
default:
break;
}
return SLine::getProperty(propertyId);
2013-02-28 15:06:54 +01:00
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool Glissando::setProperty(P_ID propertyId, const QVariant& v)
{
switch (propertyId) {
2014-05-26 18:18:01 +02:00
case P_ID::GLISS_TYPE:
setGlissandoType(Type(v.toInt()));
2013-02-28 15:06:54 +01:00
break;
2014-05-26 18:18:01 +02:00
case P_ID::GLISS_TEXT:
2013-02-28 15:06:54 +01:00
setText(v.toString());
break;
2014-05-26 18:18:01 +02:00
case P_ID::GLISS_SHOW_TEXT:
2013-02-28 15:06:54 +01:00
setShowText(v.toBool());
break;
case P_ID::GLISSANDO_STYLE:
setGlissandoStyle(MScore::GlissandoStyle(v.toInt()));
break;
case P_ID::PLAY:
setPlayGlissando(v.toBool());
break;
2013-02-28 15:06:54 +01:00
default:
if (!SLine::setProperty(propertyId, v))
2013-02-28 15:06:54 +01:00
return false;
break;
}
2016-03-02 13:20:19 +01:00
score()->setLayoutAll();
2013-02-28 15:06:54 +01:00
return true;
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant Glissando::propertyDefault(P_ID propertyId) const
{
switch (propertyId) {
2014-05-26 18:18:01 +02:00
case P_ID::GLISS_TYPE:
return int(Type::STRAIGHT);
2014-05-26 18:18:01 +02:00
case P_ID::GLISS_TEXT:
2013-02-28 15:06:54 +01:00
return "gliss.";
2014-05-26 18:18:01 +02:00
case P_ID::GLISS_SHOW_TEXT:
2013-02-28 15:06:54 +01:00
return true;
case P_ID::GLISSANDO_STYLE:
return int(MScore::GlissandoStyle::CHROMATIC);
case P_ID::PLAY:
return true;
2013-02-28 15:06:54 +01:00
default:
break;
}
return SLine::propertyDefault(propertyId);
2013-02-28 15:06:54 +01:00
}
2013-05-13 18:49:17 +02:00
}