830 lines
28 KiB
C++
830 lines
28 KiB
C++
/*
|
|
* SPDX-License-Identifier: GPL-3.0-only
|
|
* MuseScore-CLA-applies
|
|
*
|
|
* MuseScore
|
|
* Music Composition & Notation
|
|
*
|
|
* Copyright (C) 2021 MuseScore BVBA and others
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "ambitus.h"
|
|
|
|
#include "draw/pen.h"
|
|
#include "rw/compat/read206.h"
|
|
#include "io/xml.h"
|
|
|
|
#include "factory.h"
|
|
#include "chord.h"
|
|
#include "measure.h"
|
|
#include "score.h"
|
|
#include "segment.h"
|
|
#include "staff.h"
|
|
#include "stafftype.h"
|
|
#include "system.h"
|
|
#include "utils.h"
|
|
|
|
using namespace mu;
|
|
using namespace mu::engraving;
|
|
|
|
namespace Ms {
|
|
static const NoteHead::Group NOTEHEADGROUP_DEFAULT = NoteHead::Group::HEAD_NORMAL;
|
|
static const NoteHead::Type NOTEHEADTYPE_DEFAULT = NoteHead::Type::HEAD_AUTO;
|
|
static const MScore::DirectionH DIR_DEFAULT = MScore::DirectionH::AUTO;
|
|
static const bool HASLINE_DEFAULT = true;
|
|
static const Spatium LINEWIDTH_DEFAULT(0.12);
|
|
static const qreal LINEOFFSET_DEFAULT = 0.8; // the distance between notehead and line
|
|
|
|
//---------------------------------------------------------
|
|
// Ambitus
|
|
//---------------------------------------------------------
|
|
|
|
Ambitus::Ambitus(Segment* parent)
|
|
: EngravingItem(ElementType::AMBITUS, parent, ElementFlag::ON_STAFF)
|
|
{
|
|
_noteHeadGroup = NOTEHEADGROUP_DEFAULT;
|
|
_noteHeadType = NOTEHEADTYPE_DEFAULT;
|
|
_dir = DIR_DEFAULT;
|
|
_hasLine = HASLINE_DEFAULT;
|
|
_lineWidth = LINEWIDTH_DEFAULT;
|
|
_topPitch = INVALID_PITCH;
|
|
_bottomPitch = INVALID_PITCH;
|
|
_topTpc = Tpc::TPC_INVALID;
|
|
_bottomTpc = Tpc::TPC_INVALID;
|
|
|
|
_topAccid = Factory::createAccidental(parent);
|
|
_bottomAccid = Factory::createAccidental(parent);
|
|
_topAccid->setParent(this);
|
|
_bottomAccid->setParent(this);
|
|
}
|
|
|
|
Ambitus::~Ambitus()
|
|
{
|
|
delete _topAccid;
|
|
delete _bottomAccid;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mag
|
|
//---------------------------------------------------------
|
|
|
|
qreal Ambitus::mag() const
|
|
{
|
|
return staff() ? staff()->staffMag(tick()) : 1.0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// initFrom
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::initFrom(Ambitus* a)
|
|
{
|
|
_noteHeadGroup = a->_noteHeadGroup;
|
|
_noteHeadType = a->_noteHeadType;
|
|
_dir = a->_dir;
|
|
_hasLine = a->_hasLine;
|
|
_lineWidth = a->_lineWidth;
|
|
_topPitch = a->_topPitch;
|
|
_bottomPitch = a->_bottomPitch;
|
|
_topTpc = a->_topTpc;
|
|
_bottomTpc = a->_bottomTpc;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setTrack
|
|
//
|
|
// when the Ambitus element is assigned a track,
|
|
// initialize top and bottom 'notes' to top and bottom staff lines
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::setTrack(int t)
|
|
{
|
|
Segment* segm = segment();
|
|
Staff* stf = score()->staff(track2staff(t));
|
|
|
|
EngravingItem::setTrack(t);
|
|
// if not initialized and there is a segment and a staff,
|
|
// initialize pitches and tpc's to first and last staff line
|
|
// (for use in palettes)
|
|
if (_topPitch == INVALID_PITCH || _topTpc == Tpc::TPC_INVALID
|
|
|| _bottomPitch == INVALID_PITCH || _bottomTpc == Tpc::TPC_INVALID) {
|
|
if (segm && stf) {
|
|
Ambitus::Ranges ranges = estimateRanges();
|
|
_topTpc = ranges.topTpc;
|
|
_bottomTpc = ranges.bottomTpc;
|
|
_topPitch = ranges.topPitch;
|
|
_bottomPitch = ranges.bottomPitch;
|
|
|
|
_topAccid->setTrack(t);
|
|
_bottomAccid->setTrack(t);
|
|
}
|
|
// else {
|
|
// _topPitch = _bottomPitch = INVALID_PITCH;
|
|
// _topTpc = _bottomTpc = Tpc::TPC_INVALID;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setTop/BottomPitch
|
|
//
|
|
// setting either pitch requires to adjust the corresponding tpc
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::setTopPitch(int val)
|
|
{
|
|
int deltaPitch = val - topPitch();
|
|
// if deltaPitch is not an integer number of octaves, adjust tpc
|
|
// (to avoid 'wild' tpc changes with octave changes)
|
|
if (deltaPitch % PITCH_DELTA_OCTAVE != 0) {
|
|
int newTpc = topTpc() + deltaPitch * TPC_DELTA_SEMITONE;
|
|
// reduce newTpc into acceptable range via enharmonic
|
|
while (newTpc < Tpc::TPC_MIN) {
|
|
newTpc += TPC_DELTA_ENHARMONIC;
|
|
}
|
|
while (newTpc > Tpc::TPC_MAX) {
|
|
newTpc -= TPC_DELTA_ENHARMONIC;
|
|
}
|
|
_topTpc = newTpc;
|
|
}
|
|
_topPitch = val;
|
|
normalize();
|
|
}
|
|
|
|
void Ambitus::setBottomPitch(int val)
|
|
{
|
|
int deltaPitch = val - bottomPitch();
|
|
// if deltaPitch is not an integer number of octaves, adjust tpc
|
|
// (to avoid 'wild' tpc changes with octave changes)
|
|
if (deltaPitch % PITCH_DELTA_OCTAVE != 0) {
|
|
int newTpc = bottomTpc() + deltaPitch * TPC_DELTA_SEMITONE;
|
|
// reduce newTpc into acceptable range via enharmonic
|
|
while (newTpc < Tpc::TPC_MIN) {
|
|
newTpc += TPC_DELTA_ENHARMONIC;
|
|
}
|
|
while (newTpc > Tpc::TPC_MAX) {
|
|
newTpc -= TPC_DELTA_ENHARMONIC;
|
|
}
|
|
_bottomTpc = newTpc;
|
|
}
|
|
_bottomPitch= val;
|
|
normalize();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setTop/BottomTpc
|
|
//
|
|
// setting either tpc requires to adjust the corresponding pitch
|
|
// (but remaining in the same octave)
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::setTopTpc(int val)
|
|
{
|
|
int octave = topPitch() / PITCH_DELTA_OCTAVE;
|
|
int deltaTpc = val - topTpc();
|
|
// get new pitch according to tpc change
|
|
int newPitch = topPitch() + deltaTpc * TPC_DELTA_SEMITONE;
|
|
// reduce pitch to the same octave as original pitch
|
|
newPitch = (octave * PITCH_DELTA_OCTAVE) + (newPitch % PITCH_DELTA_OCTAVE);
|
|
_topPitch = newPitch;
|
|
_topTpc = val;
|
|
normalize();
|
|
}
|
|
|
|
void Ambitus::setBottomTpc(int val)
|
|
{
|
|
int octave = bottomPitch() / PITCH_DELTA_OCTAVE;
|
|
int deltaTpc = val - bottomTpc();
|
|
// get new pitch according to tpc change
|
|
int newPitch = bottomPitch() + deltaTpc * TPC_DELTA_SEMITONE;
|
|
// reduce pitch to the same octave as original pitch
|
|
newPitch = (octave * PITCH_DELTA_OCTAVE) + (newPitch % PITCH_DELTA_OCTAVE);
|
|
_bottomPitch= newPitch;
|
|
_bottomTpc = val;
|
|
normalize();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// write
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::write(XmlWriter& xml) const
|
|
{
|
|
xml.startObject(this);
|
|
xml.tag(Pid::HEAD_GROUP, int(_noteHeadGroup), int(NOTEHEADGROUP_DEFAULT));
|
|
xml.tag(Pid::HEAD_TYPE, int(_noteHeadType), int(NOTEHEADTYPE_DEFAULT));
|
|
xml.tag(Pid::MIRROR_HEAD, int(_dir), int(DIR_DEFAULT));
|
|
xml.tag("hasLine", _hasLine, true);
|
|
xml.tag(Pid::LINE_WIDTH_SPATIUM, _lineWidth, LINEWIDTH_DEFAULT);
|
|
xml.tag("topPitch", _topPitch);
|
|
xml.tag("topTpc", _topTpc);
|
|
xml.tag("bottomPitch", _bottomPitch);
|
|
xml.tag("bottomTpc", _bottomTpc);
|
|
if (_topAccid->accidentalType() != AccidentalType::NONE) {
|
|
xml.startObject("topAccidental");
|
|
_topAccid->write(xml);
|
|
xml.endObject();
|
|
}
|
|
if (_bottomAccid->accidentalType() != AccidentalType::NONE) {
|
|
xml.startObject("bottomAccidental");
|
|
_bottomAccid->write(xml);
|
|
xml.endObject();
|
|
}
|
|
EngravingItem::writeProperties(xml);
|
|
xml.endObject();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// read
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::read(XmlReader& e)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
if (!readProperties(e)) {
|
|
e.unknown();
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readProperties
|
|
//---------------------------------------------------------
|
|
|
|
bool Ambitus::readProperties(XmlReader& e)
|
|
{
|
|
const QStringRef& tag(e.name());
|
|
if (tag == "head") {
|
|
readProperty(e, Pid::HEAD_GROUP);
|
|
} else if (tag == "headType") {
|
|
readProperty(e, Pid::HEAD_TYPE);
|
|
} else if (tag == "mirror") {
|
|
readProperty(e, Pid::MIRROR_HEAD);
|
|
} else if (tag == "hasLine") {
|
|
setHasLine(e.readInt());
|
|
} else if (tag == "lineWidth") {
|
|
readProperty(e, Pid::LINE_WIDTH_SPATIUM);
|
|
} else if (tag == "topPitch") {
|
|
_topPitch = e.readInt();
|
|
} else if (tag == "bottomPitch") {
|
|
_bottomPitch = e.readInt();
|
|
} else if (tag == "topTpc") {
|
|
_topTpc = e.readInt();
|
|
} else if (tag == "bottomTpc") {
|
|
_bottomTpc = e.readInt();
|
|
} else if (tag == "topAccidental") {
|
|
while (e.readNextStartElement()) {
|
|
if (e.name() == "Accidental") {
|
|
if (score()->mscVersion() < 301) {
|
|
compat::Read206::readAccidental206(_topAccid, e);
|
|
} else {
|
|
_topAccid->read(e);
|
|
}
|
|
} else {
|
|
e.skipCurrentElement();
|
|
}
|
|
}
|
|
} else if (tag == "bottomAccidental") {
|
|
while (e.readNextStartElement()) {
|
|
if (e.name() == "Accidental") {
|
|
if (score()->mscVersion() < 301) {
|
|
compat::Read206::readAccidental206(_bottomAccid, e);
|
|
} else {
|
|
_bottomAccid->read(e);
|
|
}
|
|
} else {
|
|
e.skipCurrentElement();
|
|
}
|
|
}
|
|
} else if (EngravingItem::readProperties(e)) {
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layout
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::layout()
|
|
{
|
|
int bottomLine, topLine;
|
|
ClefType clf;
|
|
qreal headWdt = headWidth();
|
|
Key key;
|
|
qreal lineDist;
|
|
int numOfLines;
|
|
Segment* segm = segment();
|
|
qreal _spatium = spatium();
|
|
Staff* stf = nullptr;
|
|
if (segm && track() > -1) {
|
|
Fraction tick = segm->tick();
|
|
stf = score()->staff(staffIdx());
|
|
lineDist = stf->lineDistance(tick) * _spatium;
|
|
numOfLines = stf->lines(tick);
|
|
clf = stf->clef(tick);
|
|
} else { // for use in palettes
|
|
lineDist = _spatium;
|
|
numOfLines = 3;
|
|
clf = ClefType::G;
|
|
}
|
|
|
|
//
|
|
// NOTEHEADS Y POS
|
|
//
|
|
// if pitch == INVALID_PITCH oor tpc == INALID_TPC, set to some default:
|
|
// for use in palettes and when actual range cannot be calculated (new ambitus or no notes in staff)
|
|
//
|
|
qreal xAccidOffTop = 0;
|
|
qreal xAccidOffBottom = 0;
|
|
if (stf) {
|
|
key = stf->key(segm->tick());
|
|
} else {
|
|
key = Key::C;
|
|
}
|
|
|
|
// top notehead
|
|
if (_topPitch == INVALID_PITCH || _topTpc == Tpc::TPC_INVALID) {
|
|
_topPos.setY(0.0); // if uninitialized, set to top staff line
|
|
} else {
|
|
topLine = absStep(_topTpc, _topPitch);
|
|
topLine = relStep(topLine, clf);
|
|
_topPos.setY(topLine * lineDist * 0.5);
|
|
// compute accidental
|
|
AccidentalType accidType;
|
|
// if (13 <= (tpc - key) <= 19) there is no accidental)
|
|
if (_topTpc - int(key) >= 13 && _topTpc - int(key) <= 19) {
|
|
accidType = AccidentalType::NONE;
|
|
} else {
|
|
AccidentalVal accidVal = tpc2alter(_topTpc);
|
|
accidType = Accidental::value2subtype(accidVal);
|
|
if (accidType == AccidentalType::NONE) {
|
|
accidType = AccidentalType::NATURAL;
|
|
}
|
|
}
|
|
_topAccid->setAccidentalType(accidType);
|
|
if (accidType != AccidentalType::NONE) {
|
|
_topAccid->layout();
|
|
} else {
|
|
_topAccid->setbbox(RectF());
|
|
}
|
|
_topAccid->rypos() = _topPos.y();
|
|
}
|
|
|
|
// bottom notehead
|
|
if (_bottomPitch == INVALID_PITCH || _bottomTpc == Tpc::TPC_INVALID) {
|
|
_bottomPos.setY((numOfLines - 1) * lineDist); // if uninitialized, set to last staff line
|
|
} else {
|
|
bottomLine = absStep(_bottomTpc, _bottomPitch);
|
|
bottomLine = relStep(bottomLine, clf);
|
|
_bottomPos.setY(bottomLine * lineDist * 0.5);
|
|
// compute accidental
|
|
AccidentalType accidType;
|
|
if (_bottomTpc - int(key) >= 13 && _bottomTpc - int(key) <= 19) {
|
|
accidType = AccidentalType::NONE;
|
|
} else {
|
|
AccidentalVal accidVal = tpc2alter(_bottomTpc);
|
|
accidType = Accidental::value2subtype(accidVal);
|
|
if (accidType == AccidentalType::NONE) {
|
|
accidType = AccidentalType::NATURAL;
|
|
}
|
|
}
|
|
_bottomAccid->setAccidentalType(accidType);
|
|
if (accidType != AccidentalType::NONE) {
|
|
_bottomAccid->layout();
|
|
} else {
|
|
_bottomAccid->setbbox(RectF());
|
|
}
|
|
_bottomAccid->rypos() = _bottomPos.y();
|
|
}
|
|
|
|
//
|
|
// NOTEHEAD X POS
|
|
//
|
|
// Note: manages colliding accidentals
|
|
//
|
|
qreal accNoteDist = point(score()->styleS(Sid::accidentalNoteDistance));
|
|
xAccidOffTop = _topAccid->width() + accNoteDist;
|
|
xAccidOffBottom = _bottomAccid->width() + accNoteDist;
|
|
|
|
// if top accidental extends down more than bottom accidental extends up,
|
|
// AND ambitus is not leaning right, bottom accidental needs to be displaced
|
|
bool collision
|
|
=(_topAccid->ipos().y() + _topAccid->bbox().y() + _topAccid->height()
|
|
> _bottomAccid->ipos().y() + _bottomAccid->bbox().y())
|
|
&& _dir != MScore::DirectionH::RIGHT;
|
|
if (collision) {
|
|
// displace bottom accidental (also attempting to 'undercut' flats)
|
|
xAccidOffBottom = xAccidOffTop
|
|
+ ((_bottomAccid->accidentalType() == AccidentalType::FLAT
|
|
|| _bottomAccid->accidentalType() == AccidentalType::FLAT2
|
|
|| _bottomAccid->accidentalType() == AccidentalType::NATURAL)
|
|
? _bottomAccid->width() * 0.5 : _bottomAccid->width());
|
|
}
|
|
|
|
switch (_dir) {
|
|
case MScore::DirectionH::AUTO: // noteheads one above the other
|
|
// left align noteheads and right align accidentals 'hanging' on the left
|
|
_topPos.setX(0.0);
|
|
_bottomPos.setX(0.0);
|
|
_topAccid->rxpos() = -xAccidOffTop;
|
|
_bottomAccid->rxpos() = -xAccidOffBottom;
|
|
break;
|
|
case MScore::DirectionH::LEFT: // top notehead at the left of bottom notehead
|
|
// place top notehead at left margin; bottom notehead at right of top head;
|
|
// top accid. 'hanging' on left of top head and bottom accid. 'hanging' at left of bottom head
|
|
_topPos.setX(0.0);
|
|
_bottomPos.setX(headWdt);
|
|
_topAccid->rxpos() = -xAccidOffTop;
|
|
_bottomAccid->rxpos() = collision ? -xAccidOffBottom : headWdt - xAccidOffBottom;
|
|
break;
|
|
case MScore::DirectionH::RIGHT: // top notehead at the right of bottom notehead
|
|
// bottom notehead at left margin; top notehead at right of bottomnotehead
|
|
// top accid. 'hanging' on left of top head and bottom accid. 'hanging' at left of bottom head
|
|
_bottomPos.setX(0.0);
|
|
_topPos.setX(headWdt);
|
|
_bottomAccid->rxpos() = -xAccidOffBottom;
|
|
_topAccid->rxpos() = headWdt - xAccidOffTop;
|
|
break;
|
|
}
|
|
|
|
// compute line from top note centre to bottom note centre
|
|
mu::LineF fullLine(_topPos.x() + headWdt * 0.5, _topPos.y(), _bottomPos.x() + headWdt * 0.5, _bottomPos.y());
|
|
// shorten line on each side by offsets
|
|
qreal yDelta = _bottomPos.y() - _topPos.y();
|
|
if (yDelta != 0.0) {
|
|
qreal off = _spatium * LINEOFFSET_DEFAULT;
|
|
mu::PointF p1 = fullLine.pointAt(off / yDelta);
|
|
mu::PointF p2 = fullLine.pointAt(1 - (off / yDelta));
|
|
_line = mu::LineF(p1, p2);
|
|
} else {
|
|
_line = fullLine;
|
|
}
|
|
|
|
mu::RectF headRect(0, -0.5 * _spatium, headWdt, 1 * _spatium);
|
|
setbbox(headRect.translated(_topPos).united(headRect.translated(_bottomPos))
|
|
.united(_topAccid->bbox().translated(_topAccid->ipos()))
|
|
.united(_bottomAccid->bbox().translated(_bottomAccid->ipos()))
|
|
);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// draw
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::draw(mu::draw::Painter* painter) const
|
|
{
|
|
TRACE_OBJ_DRAW;
|
|
using namespace mu::draw;
|
|
qreal _spatium = spatium();
|
|
qreal lw = lineWidth().val() * _spatium;
|
|
painter->setPen(Pen(curColor(), lw, PenStyle::SolidLine, PenCapStyle::FlatCap));
|
|
drawSymbol(noteHead(), painter, _topPos);
|
|
drawSymbol(noteHead(), painter, _bottomPos);
|
|
if (_hasLine) {
|
|
painter->drawLine(_line);
|
|
}
|
|
|
|
// draw ledger lines (if not in a palette)
|
|
if (segment() && track() > -1) {
|
|
Fraction tick = segment()->tick();
|
|
Staff* staff = score()->staff(staffIdx());
|
|
qreal lineDist = staff->lineDistance(tick);
|
|
int numOfLines = staff->lines(tick);
|
|
qreal step = lineDist * _spatium;
|
|
qreal stepTolerance = step * 0.1;
|
|
qreal ledgerLineLength = score()->styleS(Sid::ledgerLineLength).val() * _spatium;
|
|
qreal ledgerLineWidth = score()->styleS(Sid::ledgerLineWidth).val() * _spatium;
|
|
painter->setPen(Pen(curColor(), ledgerLineWidth, PenStyle::SolidLine, PenCapStyle::FlatCap));
|
|
|
|
if (_topPos.y() - stepTolerance <= -step) {
|
|
qreal xMin = _topPos.x() - ledgerLineLength;
|
|
qreal xMax = _topPos.x() + headWidth() + ledgerLineLength;
|
|
for (qreal y = -step; y >= _topPos.y() - stepTolerance; y -= step) {
|
|
painter->drawLine(mu::PointF(xMin, y), mu::PointF(xMax, y));
|
|
}
|
|
}
|
|
|
|
if (_bottomPos.y() + stepTolerance >= numOfLines * step) {
|
|
qreal xMin = _bottomPos.x() - ledgerLineLength;
|
|
qreal xMax = _bottomPos.x() + headWidth() + ledgerLineLength;
|
|
for (qreal y = numOfLines * step; y <= _bottomPos.y() + stepTolerance; y += step) {
|
|
painter->drawLine(mu::PointF(xMin, y), mu::PointF(xMax, y));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// scanElements
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::scanElements(void* data, void (* func)(void*, EngravingItem*), bool all)
|
|
{
|
|
Q_UNUSED(all);
|
|
EngravingObject::scanElements(data, func, all);
|
|
func(data, this);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// noteHead
|
|
//---------------------------------------------------------
|
|
|
|
SymId Ambitus::noteHead() const
|
|
{
|
|
int hg = 1;
|
|
NoteHead::Type ht = NoteHead::Type::HEAD_QUARTER;
|
|
|
|
if (_noteHeadType != NoteHead::Type::HEAD_AUTO) {
|
|
ht = _noteHeadType;
|
|
}
|
|
|
|
SymId t = Note::noteHead(hg, _noteHeadGroup, ht);
|
|
if (t == SymId::noSym) {
|
|
qDebug("invalid notehead %d/%d", int(_noteHeadGroup), int(_noteHeadType));
|
|
t = Note::noteHead(0, NoteHead::Group::HEAD_NORMAL, ht);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// headWidth
|
|
//
|
|
// returns the width of the notehead symbol
|
|
//---------------------------------------------------------
|
|
|
|
qreal Ambitus::headWidth() const
|
|
{
|
|
// int head = noteHead();
|
|
// qreal val = symbols[score()->symIdx()][head].width(magS());
|
|
// return val;
|
|
return symWidth(noteHead());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// pagePos
|
|
//---------------------------------------------------------
|
|
|
|
mu::PointF Ambitus::pagePos() const
|
|
{
|
|
if (parent() == 0) {
|
|
return pos();
|
|
}
|
|
System* system = segment()->measure()->system();
|
|
qreal yp = y();
|
|
if (system) {
|
|
yp += system->staff(staffIdx())->y() + system->y();
|
|
}
|
|
return PointF(pageX(), yp);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// normalize
|
|
//
|
|
// makes sure _topPitch is not < _bottomPitch
|
|
//---------------------------------------------------------
|
|
|
|
void Ambitus::normalize()
|
|
{
|
|
if (_topPitch < _bottomPitch) {
|
|
int temp = _topPitch;
|
|
_topPitch = _bottomPitch;
|
|
_bottomPitch= temp;
|
|
temp = _topTpc;
|
|
_topTpc = _bottomTpc;
|
|
_bottomTpc = temp;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// updateRange
|
|
//
|
|
// scans the staff contents up to next section break to update the range pitches/tpc's
|
|
//---------------------------------------------------------
|
|
|
|
Ambitus::Ranges Ambitus::estimateRanges() const
|
|
{
|
|
Ambitus::Ranges result;
|
|
|
|
if (!segment()) {
|
|
return result;
|
|
}
|
|
Chord* chord;
|
|
int firstTrack = track();
|
|
int lastTrack = firstTrack + VOICES - 1;
|
|
int pitchTop = -1000;
|
|
int pitchBottom = 1000;
|
|
int tpcTop = 0; // Initialized to prevent warning
|
|
int tpcBottom = 0; // Initialized to prevent warning
|
|
int trk;
|
|
Measure* meas = segment()->measure();
|
|
Segment* segm = meas->findSegment(SegmentType::ChordRest, segment()->tick());
|
|
bool stop = meas->sectionBreak();
|
|
while (segm) {
|
|
// moved to another measure?
|
|
if (segm->measure() != meas) {
|
|
// if section break has been found, stop here
|
|
if (stop) {
|
|
break;
|
|
}
|
|
// update meas and stop condition
|
|
meas = segm->measure();
|
|
stop = meas->sectionBreak();
|
|
}
|
|
// scan all relevant tracks of this segment for chords
|
|
for (trk = firstTrack; trk <= lastTrack; trk++) {
|
|
EngravingItem* e = segm->element(trk);
|
|
if (!e || !e->isChord()) {
|
|
continue;
|
|
}
|
|
chord = toChord(e);
|
|
// update pitch range (with associated tpc's)
|
|
for (Note* n : chord->notes()) {
|
|
if (!n->play()) { // skip notes which are not to be played
|
|
continue;
|
|
}
|
|
int pitch = n->epitch();
|
|
if (pitch > pitchTop) {
|
|
pitchTop = pitch;
|
|
tpcTop = n->tpc();
|
|
}
|
|
if (pitch < pitchBottom) {
|
|
pitchBottom = pitch;
|
|
tpcBottom = n->tpc();
|
|
}
|
|
}
|
|
}
|
|
segm = segm->nextCR();
|
|
}
|
|
|
|
if (pitchTop > -1000) { // if something has been found, update this
|
|
result.topPitch = pitchTop;
|
|
result.bottomPitch = pitchBottom;
|
|
result.topTpc = tpcTop;
|
|
result.bottomTpc = tpcBottom;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getProperty
|
|
//---------------------------------------------------------
|
|
|
|
QVariant Ambitus::getProperty(Pid propertyId) const
|
|
{
|
|
switch (propertyId) {
|
|
case Pid::HEAD_GROUP:
|
|
return int(noteHeadGroup());
|
|
case Pid::HEAD_TYPE:
|
|
return int(noteHeadType());
|
|
case Pid::MIRROR_HEAD:
|
|
return int(direction());
|
|
case Pid::GHOST: // recycled property = _hasLine
|
|
return hasLine();
|
|
case Pid::LINE_WIDTH_SPATIUM:
|
|
return lineWidth();
|
|
case Pid::TPC1:
|
|
return topTpc();
|
|
case Pid::FBPARENTHESIS1: // recycled property = _bottomTpc
|
|
return bottomTpc();
|
|
case Pid::PITCH:
|
|
return topPitch();
|
|
case Pid::FBPARENTHESIS2: // recycled property = _bottomPitch
|
|
return bottomPitch();
|
|
case Pid::FBPARENTHESIS3: // recycled property = octave of _topPitch
|
|
return topOctave();
|
|
case Pid::FBPARENTHESIS4: // recycled property = octave of _bottomPitch
|
|
return bottomOctave();
|
|
default:
|
|
return EngravingItem::getProperty(propertyId);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setProperty
|
|
//---------------------------------------------------------
|
|
|
|
bool Ambitus::setProperty(Pid propertyId, const QVariant& v)
|
|
{
|
|
switch (propertyId) {
|
|
case Pid::HEAD_GROUP:
|
|
setNoteHeadGroup(NoteHead::Group(v.toInt()));
|
|
break;
|
|
case Pid::HEAD_TYPE:
|
|
setNoteHeadType(NoteHead::Type(v.toInt()));
|
|
break;
|
|
case Pid::MIRROR_HEAD:
|
|
setDirection(MScore::DirectionH(v.toInt()));
|
|
break;
|
|
case Pid::GHOST: // recycled property = _hasLine
|
|
setHasLine(v.toBool());
|
|
break;
|
|
case Pid::LINE_WIDTH_SPATIUM:
|
|
setLineWidth(v.value<Spatium>());
|
|
break;
|
|
case Pid::TPC1:
|
|
setTopTpc(v.toInt());
|
|
break;
|
|
case Pid::FBPARENTHESIS1: // recycled property = _bottomTpc
|
|
setBottomTpc(v.toInt());
|
|
break;
|
|
case Pid::PITCH:
|
|
setTopPitch(v.toInt());
|
|
break;
|
|
case Pid::FBPARENTHESIS2: // recycled property = _bottomPitch
|
|
setBottomPitch(v.toInt());
|
|
break;
|
|
case Pid::FBPARENTHESIS3: // recycled property = octave of _topPitch
|
|
setTopPitch(topPitch() % 12 + (v.toInt() + 1) * 12);
|
|
break;
|
|
case Pid::FBPARENTHESIS4: // recycled property = octave of _bottomPitch
|
|
setBottomPitch(bottomPitch() % 12 + (v.toInt() + 1) * 12);
|
|
break;
|
|
default:
|
|
return EngravingItem::setProperty(propertyId, v);
|
|
}
|
|
triggerLayout();
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// propertyDefault
|
|
//---------------------------------------------------------
|
|
|
|
QVariant Ambitus::propertyDefault(Pid id) const
|
|
{
|
|
switch (id) {
|
|
case Pid::HEAD_GROUP:
|
|
return int(NOTEHEADGROUP_DEFAULT);
|
|
case Pid::HEAD_TYPE:
|
|
return int(NOTEHEADTYPE_DEFAULT);
|
|
case Pid::MIRROR_HEAD:
|
|
return int(DIR_DEFAULT);
|
|
case Pid::GHOST:
|
|
return HASLINE_DEFAULT;
|
|
case Pid::LINE_WIDTH_SPATIUM:
|
|
return Spatium(LINEWIDTH_DEFAULT);
|
|
case Pid::TPC1:
|
|
return estimateRanges().topTpc;
|
|
case Pid::FBPARENTHESIS1:
|
|
return estimateRanges().bottomTpc;
|
|
case Pid::PITCH:
|
|
return estimateRanges().topPitch;
|
|
case Pid::FBPARENTHESIS2:
|
|
return estimateRanges().bottomPitch;
|
|
case Pid::FBPARENTHESIS3:
|
|
return int(estimateRanges().topPitch / 12) - 1;
|
|
case Pid::FBPARENTHESIS4:
|
|
return int(estimateRanges().bottomPitch / 12) - 1;
|
|
default:
|
|
return EngravingItem::propertyDefault(id);
|
|
}
|
|
//return QVariant();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// nextSegmentElement
|
|
//---------------------------------------------------------
|
|
|
|
EngravingItem* Ambitus::nextSegmentElement()
|
|
{
|
|
return segment()->firstInNextSegments(staffIdx());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// prevSegmentElement
|
|
//---------------------------------------------------------
|
|
|
|
EngravingItem* Ambitus::prevSegmentElement()
|
|
{
|
|
return segment()->lastInPrevSegments(staffIdx());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// accessibleInfo
|
|
//---------------------------------------------------------
|
|
|
|
QString Ambitus::accessibleInfo() const
|
|
{
|
|
return QObject::tr("%1; Top pitch: %2%3; Bottom pitch: %4%5").arg(EngravingItem::accessibleInfo(),
|
|
tpc2name(topTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO,
|
|
false),
|
|
QString::number(topOctave()),
|
|
tpc2name(bottomTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO,
|
|
false),
|
|
QString::number(bottomOctave()));
|
|
}
|
|
}
|