MuseScore/libmscore/clef.cpp
2020-05-29 21:15:25 +02:00

580 lines
23 KiB
C++

//=============================================================================
// MuseScore
// Music Composition & Notation
//
// 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 Clef (partial) and ClefList (complete).
*/
#include "clef.h"
#include "measure.h"
#include "ambitus.h"
#include "xml.h"
#include "sym.h"
#include "symbol.h"
#include "system.h"
#include "score.h"
#include "staff.h"
#include "segment.h"
#include "stafftype.h"
namespace Ms {
// table must be in sync with enum ClefType
const ClefInfo ClefInfo::clefTable[] = {
// tag xmlName line oCh pOff|-lines for sharps---||---lines for flats--| symbol | name | valid in staff group
{ "G", "G", 2, 0, 45, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::gClef,
QT_TRANSLATE_NOOP("clefTable", "Treble clef"), StaffGroup::STANDARD },
// { "G15mb","G", 2, -2, 59, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::gClef15mb, QT_TRANSLATE_NOOP("clefTable", "Treble clef 15ma bassa"), StaffGroup::STANDARD },
{ "G15mb","G", 2, -2, 31, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::gClef15mb,
QT_TRANSLATE_NOOP("clefTable", "Treble clef 15ma bassa"), StaffGroup::STANDARD },
{ "G8vb", "G", 2, -1, 38, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::gClef8vb,
QT_TRANSLATE_NOOP("clefTable", "Treble clef 8va bassa"), StaffGroup::STANDARD },
{ "G8va", "G", 2, 1, 52, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::gClef8va,
QT_TRANSLATE_NOOP("clefTable", "Treble clef 8va alta"), StaffGroup::STANDARD },
{ "G15ma","G", 2, 2, 59, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::gClef15ma,
QT_TRANSLATE_NOOP("clefTable", "Treble clef 15ma alta"), StaffGroup::STANDARD },
{ "G8vbo","G", 2, -1, 38, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::gClef8vbOld,
QT_TRANSLATE_NOOP("clefTable", "Double treble clef 8va bassa on 2nd line"),StaffGroup::STANDARD },
{ "G8vbp","G", 2, 0, 45, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::gClef8vbParens,
QT_TRANSLATE_NOOP("clefTable", "Treble clef optional 8va bassa"), StaffGroup::STANDARD },
{ "G1", "G", 1, 0, 47, { 2, 5, 1, 4, 7, 3, 6, 6, 3, 7, 4, 8, 5, 9 }, SymId::gClef,
QT_TRANSLATE_NOOP("clefTable", "French violin clef"), StaffGroup::STANDARD },
{ "C1", "C", 1, 0, 43, { 5, 1, 4, 0, 3,-1, 2, 2, 6, 3, 7, 4, 8, 5 }, SymId::cClef,
QT_TRANSLATE_NOOP("clefTable", "Soprano clef"), StaffGroup::STANDARD },
{ "C2", "C", 2, 0, 41, { 3, 6, 2, 5, 1, 4, 0, 0, 4, 1, 5, 2, 6, 3 }, SymId::cClef,
QT_TRANSLATE_NOOP("clefTable", "Mezzo-soprano clef"), StaffGroup::STANDARD },
{ "C3", "C", 3, 0, 39, { 1, 4, 0, 3, 6, 2, 5, 5, 2, 6, 3, 7, 4, 8 }, SymId::cClef,
QT_TRANSLATE_NOOP("clefTable", "Alto clef"), StaffGroup::STANDARD },
{ "C4", "C", 4, 0, 37, { 6, 2, 5, 1, 4, 0, 3, 3, 0, 4, 1, 5, 2, 6 }, SymId::cClef,
QT_TRANSLATE_NOOP("clefTable", "Tenor clef"), StaffGroup::STANDARD },
{ "C5", "C", 5, 0, 35, { 4, 0, 3,-1, 2, 5, 1, 1, 5, 2, 6, 3, 7, 4 }, SymId::cClef,
QT_TRANSLATE_NOOP("clefTable", "Baritone clef (C clef)"), StaffGroup::STANDARD },
{ "C_19C", "G", 2, 0, 45, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::cClefSquare,
QT_TRANSLATE_NOOP("clefTable", "C clef, H shape (19th century)"), StaffGroup::STANDARD },
{ "C1_F18C", "C", 1, 0, 43, { 5, 1, 4, 0, 3,-1, 2, 2, 6, 3, 7, 4, 8, 5 }, SymId::cClefFrench,
QT_TRANSLATE_NOOP("clefTable", "Soprano clef (French, 18th century)"), StaffGroup::STANDARD },
{ "C3_F18C", "C", 3, 0, 39, { 1, 4, 0, 3, 6, 2, 5, 5, 2, 6, 3, 7, 4, 8 }, SymId::cClefFrench,
QT_TRANSLATE_NOOP("clefTable", "Alto clef (French, 18th century)"), StaffGroup::STANDARD },
{ "C4_F18C", "C", 4, 0, 37, { 6, 2, 5, 1, 4, 0, 3, 3, 0, 4, 1, 5, 2, 6 }, SymId::cClefFrench,
QT_TRANSLATE_NOOP("clefTable", "Tenor clef (French, 18th century)"), StaffGroup::STANDARD },
{ "C1_F20C", "C", 1, 0, 43, { 5, 1, 4, 0, 3,-1, 2, 2, 6, 3, 7, 4, 8, 5 }, SymId::cClefFrench20C,
QT_TRANSLATE_NOOP("clefTable", "Soprano clef (French, 20th century)"), StaffGroup::STANDARD },
{ "C3_F20C", "C", 3, 0, 39, { 1, 4, 0, 3, 6, 2, 5, 5, 2, 6, 3, 7, 4, 8 }, SymId::cClefFrench20C,
QT_TRANSLATE_NOOP("clefTable", "Alto clef (French, 20th century)"), StaffGroup::STANDARD },
{ "C4_F20C", "C", 4, 0, 37, { 6, 2, 5, 1, 4, 0, 3, 3, 0, 4, 1, 5, 2, 6 }, SymId::cClefFrench20C,
QT_TRANSLATE_NOOP("clefTable", "Tenor clef (French, 20th century)"), StaffGroup::STANDARD },
{ "F", "F", 4, 0, 33, { 2, 5, 1, 4, 7, 3, 6, 6, 3, 7, 4, 8, 5, 9 }, SymId::fClef,
QT_TRANSLATE_NOOP("clefTable", "Bass clef"), StaffGroup::STANDARD },
{ "F15mb","F", 4, -2, 19, { 2, 5, 1, 4, 7, 3, 6, 6, 3, 7, 4, 8, 5, 9 }, SymId::fClef15mb,
QT_TRANSLATE_NOOP("clefTable", "Bass clef 15ma bassa"), StaffGroup::STANDARD },
{ "F8vb", "F", 4, -1, 26, { 2, 5, 1, 4, 7, 3, 6, 6, 3, 7, 4, 8, 5, 9 }, SymId::fClef8vb,
QT_TRANSLATE_NOOP("clefTable", "Bass clef 8va bassa"), StaffGroup::STANDARD },
{ "F8va", "F", 4, 1, 40, { 2, 5, 1, 4, 7, 3, 6, 6, 3, 7, 4, 8, 5, 9 }, SymId::fClef8va,
QT_TRANSLATE_NOOP("clefTable", "Bass clef 8va alta"), StaffGroup::STANDARD },
{ "F15ma","F", 4, 2, 47, { 2, 5, 1, 4, 7, 3, 6, 6, 3, 7, 4, 8, 5, 9 }, SymId::fClef15ma,
QT_TRANSLATE_NOOP("clefTable", "Bass clef 15ma alta"), StaffGroup::STANDARD },
{ "F3", "F", 3, 0, 35, { 4, 0, 3,-1, 2, 5, 1, 1, 5, 2, 6, 3, 7, 4 }, SymId::fClef,
QT_TRANSLATE_NOOP("clefTable", "Baritone clef (F clef)"), StaffGroup::STANDARD },
{ "F5", "F", 5, 0, 31, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::fClef,
QT_TRANSLATE_NOOP("clefTable", "Subbass clef"), StaffGroup::STANDARD },
{ "F_F18C","F", 4, 0, 33, { 2, 5, 1, 4, 7, 3, 6, 6, 3, 7, 4, 8, 5, 9 }, SymId::fClefFrench,
QT_TRANSLATE_NOOP("clefTable", "F clef (French, 18th century)"), StaffGroup::STANDARD },
{ "F_19C","F", 4, 0, 33, { 2, 5, 1, 4, 7, 3, 6, 6, 3, 7, 4, 8, 5, 9 }, SymId::fClef19thCentury,
QT_TRANSLATE_NOOP("clefTable", "F clef (19th century)"), StaffGroup::STANDARD },
{ "PERC", "percussion",2, 0, 45, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::unpitchedPercussionClef1,
QT_TRANSLATE_NOOP("clefTable", "Percussion"), StaffGroup::PERCUSSION },
{ "PERC2","percussion",2, 0, 45, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::unpitchedPercussionClef2,
QT_TRANSLATE_NOOP("clefTable", "Percussion 2"), StaffGroup::PERCUSSION },
{ "TAB", "TAB", 5, 0, 0, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::sixStringTabClef,
QT_TRANSLATE_NOOP("clefTable", "Tablature"), StaffGroup::TAB },
{ "TAB4", "TAB", 5, 0, 0, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::fourStringTabClef,
QT_TRANSLATE_NOOP("clefTable", "Tablature 4 lines"), StaffGroup::TAB },
{ "TAB2", "TAB", 5, 0, 0, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::sixStringTabClefSerif,
QT_TRANSLATE_NOOP("clefTable", "Tablature Serif"), StaffGroup::TAB },
{ "TAB4_SERIF", "TAB", 5, 0, 0, { 0, 3,-1, 2, 5, 1, 4, 4, 1, 5, 2, 6, 3, 7 }, SymId::fourStringTabClefSerif,
QT_TRANSLATE_NOOP("clefTable", "Tablature Serif 4 lines"), StaffGroup::TAB },
};
//---------------------------------------------------------
// tag2type
//---------------------------------------------------------
ClefType ClefInfo::tag2type(const QString& s)
{
for (unsigned i = 0; i < sizeof(ClefInfo::clefTable) / sizeof(*ClefInfo::clefTable); ++i) {
if (clefTable[i]._tag == s) {
return ClefType(i);
}
}
return ClefType::G;
}
//---------------------------------------------------------
// Clef
//---------------------------------------------------------
Clef::Clef(Score* s)
: Element(s, ElementFlag::ON_STAFF), symId(SymId::noSym)
{}
//---------------------------------------------------------
// mag
//---------------------------------------------------------
qreal Clef::mag() const
{
qreal mag = staff() ? staff()->mag(tick()) : 1.0;
if (_small) {
mag *= score()->styleD(Sid::smallClefMag);
}
return mag;
}
//---------------------------------------------------------
// layout
//---------------------------------------------------------
void Clef::layout()
{
// determine current number of lines and line distance
int lines;
qreal lineDist;
Segment* clefSeg = segment();
int stepOffset;
// check clef visibility and type compatibility
if (clefSeg && staff()) {
Fraction tick = clefSeg->tick();
const StaffType* st = staff()->staffType(tick);
bool show = st->genClef(); // check staff type allows clef display
// check clef is compatible with staff type group:
if (ClefInfo::staffGroup(clefType()) != st->group()) {
if (tick > Fraction(0,1) && !generated()) { // if clef is not generated, hide it
show = false;
} else { // if generated, replace with initial clef type
// TODO : instead of initial staff clef (which is assumed to be compatible)
// use the last compatible clef previously found in staff
_clefTypes = staff()->clefType(Fraction(0,1));
}
}
// if clef not to show or not compatible with staff group
if (!show) {
setbbox(QRectF());
symId = SymId::noSym;
qDebug("Clef::layout(): invisible clef at tick %d(%d) staff %d",
segment()->tick().ticks(), segment()->tick().ticks() / 1920, staffIdx());
return;
}
lines = st->lines(); // init values from staff type
lineDist = st->lineDistance().val();
stepOffset = st->stepOffset();
} else {
lines = 5;
lineDist = 1.0;
stepOffset = 0;
}
qreal _spatium = spatium();
qreal yoff = 0.0;
if (clefType() != ClefType::INVALID && clefType() != ClefType::MAX) {
symId = ClefInfo::symId(clefType());
yoff = lineDist * (lines - ClefInfo::line(clefType()));
} else {
symId = SymId::noSym;
}
switch (clefType()) {
case ClefType::C_19C: // 19th C clef is like a G clef
yoff = lineDist * 1.5;
break;
case ClefType::TAB: // TAB clef
// on tablature, position clef at half the number of spaces * line distance
yoff = lineDist * (lines - 1) * .5;
break;
case ClefType::TAB4: // TAB clef 4 strings
// on tablature, position clef at half the number of spaces * line distance
yoff = lineDist * (lines - 1) * .5;
break;
case ClefType::TAB_SERIF: // TAB clef alternate style
// on tablature, position clef at half the number of spaces * line distance
yoff = lineDist * (lines - 1) * .5;
break;
case ClefType::TAB4_SERIF: // TAB clef alternate style
// on tablature, position clef at half the number of spaces * line distance
yoff = lineDist * (lines - 1) * .5;
break;
case ClefType::PERC: // percussion clefs
yoff = lineDist * (lines - 1) * 0.5;
break;
case ClefType::PERC2:
yoff = lineDist * (lines - 1) * 0.5;
break;
case ClefType::INVALID:
case ClefType::MAX:
qDebug("Clef::layout: invalid type");
return;
default:
break;
}
// clefs on palette or at start of system/measure are left aligned
// other clefs are right aligned
QRectF r(symBbox(symId));
qreal x = segment() && segment()->rtick().isNotZero() ? -r.right() : 0.0;
setPos(x, yoff * _spatium + (stepOffset * -_spatium));
setbbox(r);
}
//---------------------------------------------------------
// draw
//---------------------------------------------------------
void Clef::draw(QPainter* painter) const
{
if (symId == SymId::noSym || (staff() && !const_cast<const Staff*>(staff())->staffType(tick())->genClef())) {
return;
}
painter->setPen(curColor());
drawSymbol(symId, painter);
}
//---------------------------------------------------------
// acceptDrop
//---------------------------------------------------------
bool Clef::acceptDrop(EditData& data) const
{
return data.dropElement->type() == ElementType::CLEF
|| (/*!generated() &&*/ data.dropElement->type() == ElementType::AMBITUS);
}
//---------------------------------------------------------
// drop
//---------------------------------------------------------
Element* Clef::drop(EditData& data)
{
Element* e = data.dropElement;
Clef* c = 0;
if (e->isClef()) {
Clef* clef = toClef(e);
ClefType stype = clef->clefType();
if (clefType() != stype) {
score()->undoChangeClef(staff(), this, stype);
c = this;
}
} else if (e->isAmbitus()) {
/*if (!generated())*/
{
Measure* meas = measure();
Segment* segm = meas->getSegment(SegmentType::Ambitus, meas->tick());
if (segm->element(track())) {
score()->undoRemoveElement(segm->element(track()));
}
Ambitus* r = new Ambitus(score());
r->setParent(segm);
r->setTrack(track());
score()->undoAddElement(r);
}
}
delete e;
return c;
}
//---------------------------------------------------------
// setSmall
//---------------------------------------------------------
void Clef::setSmall(bool val)
{
if (val != _small) {
_small = val;
}
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void Clef::read(XmlReader& e)
{
while (e.readNextStartElement()) {
const QStringRef& tag(e.name());
if (tag == "concertClefType") {
_clefTypes._concertClef = Clef::clefType(e.readElementText());
} else if (tag == "transposingClefType") {
_clefTypes._transposingClef = Clef::clefType(e.readElementText());
} else if (tag == "showCourtesyClef") {
_showCourtesy = e.readInt();
} else if (tag == "forInstrumentChange") {
_forInstrumentChange = e.readBool();
} else if (!Element::readProperties(e)) {
e.unknown();
}
}
if (clefType() == ClefType::INVALID) {
setClefType(ClefType::G);
}
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void Clef::write(XmlWriter& xml) const
{
xml.stag(this);
writeProperty(xml, Pid::CLEF_TYPE_CONCERT);
writeProperty(xml, Pid::CLEF_TYPE_TRANSPOSING);
if (!_showCourtesy) {
xml.tag("showCourtesyClef", _showCourtesy);
}
if (_forInstrumentChange) {
xml.tag("forInstrumentChange", _forInstrumentChange);
}
Element::writeProperties(xml);
xml.etag();
}
//---------------------------------------------------------
// setClefType
//---------------------------------------------------------
void Clef::setClefType(const QString& s)
{
ClefType ct = clefType(s);
if (ct == ClefType::INVALID) {
qDebug("Clef::setSubtype: unknown: <%s>", qPrintable(s));
ct = ClefType::G;
}
setClefType(ct);
}
//---------------------------------------------------------
// clefTypeName
//---------------------------------------------------------
const char* Clef::clefTypeName()
{
return ClefInfo::tag(clefType());
}
//---------------------------------------------------------
// clefType
//---------------------------------------------------------
ClefType Clef::clefType(const QString& s)
{
return ClefInfo::tag2type(s);
}
//---------------------------------------------------------
// setClefType
//---------------------------------------------------------
void Clef::setClefType(ClefType i)
{
if (concertPitch()) {
_clefTypes._concertClef = i;
if (_clefTypes._transposingClef == ClefType::INVALID) {
_clefTypes._transposingClef = i;
}
} else {
_clefTypes._transposingClef = i;
if (_clefTypes._concertClef == ClefType::INVALID) {
_clefTypes._concertClef = i;
}
}
}
//---------------------------------------------------------
// setConcertClef
//---------------------------------------------------------
void Clef::setConcertClef(ClefType val)
{
_clefTypes._concertClef = val;
}
//---------------------------------------------------------
// setTransposingClef
//---------------------------------------------------------
void Clef::setTransposingClef(ClefType val)
{
_clefTypes._transposingClef = val;
}
//---------------------------------------------------------
// clefType
//---------------------------------------------------------
ClefType Clef::clefType() const
{
if (concertPitch()) {
return _clefTypes._concertClef;
} else {
return _clefTypes._transposingClef;
}
}
//---------------------------------------------------------
// spatiumChanged
//---------------------------------------------------------
void Clef::spatiumChanged(qreal oldValue, qreal newValue)
{
Element::spatiumChanged(oldValue, newValue);
layout();
}
//---------------------------------------------------------
// undoSetShowCourtesy
//---------------------------------------------------------
void Clef::undoSetShowCourtesy(bool v)
{
undoChangeProperty(Pid::SHOW_COURTESY, v);
}
//---------------------------------------------------------
// otherClef
// try to locate the 'other clef' of a courtesy / main pair
//---------------------------------------------------------
Clef* Clef::otherClef()
{
// if not in a clef-segment-measure hierarchy, do nothing
if (!parent() || !parent()->isSegment()) {
return nullptr;
}
Segment* segm = toSegment(parent());
if (!segm->parent() || !segm->parent()->isMeasure()) {
return 0;
}
Measure* meas = toMeasure(segm->parent());
Measure* otherMeas = nullptr;
Segment* otherSegm = nullptr;
Fraction segmTick = segm->tick();
SegmentType type = SegmentType::Clef;
if (segmTick == meas->tick() && segm->segmentType() == SegmentType::HeaderClef) { // if clef segm is measure-initial
otherMeas = meas->prevMeasure(); // look for a previous measure
} else if (segmTick == meas->tick() + meas->ticks()) { // if clef segm is measure-final
otherMeas = meas->nextMeasure(); // look for a next measure
type = SegmentType::HeaderClef;
}
if (!otherMeas) {
return nullptr;
}
// look for a clef segment in the 'other' measure at the same tick of this clef segment
otherSegm = otherMeas->findSegment(type, segmTick);
if (!otherSegm) {
return nullptr;
}
// if any 'other' segment found, look for a clef in the same track as this
return toClef(otherSegm->element(track()));
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant Clef::getProperty(Pid propertyId) const
{
switch (propertyId) {
case Pid::CLEF_TYPE_CONCERT: return int(_clefTypes._concertClef);
case Pid::CLEF_TYPE_TRANSPOSING: return int(_clefTypes._transposingClef);
case Pid::SHOW_COURTESY: return showCourtesy();
case Pid::SMALL: return small();
default:
return Element::getProperty(propertyId);
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool Clef::setProperty(Pid propertyId, const QVariant& v)
{
switch (propertyId) {
case Pid::CLEF_TYPE_CONCERT:
setConcertClef(ClefType(v.toInt()));
break;
case Pid::CLEF_TYPE_TRANSPOSING:
setTransposingClef(ClefType(v.toInt()));
break;
case Pid::SHOW_COURTESY: _showCourtesy = v.toBool();
break;
case Pid::SMALL: setSmall(v.toBool());
break;
default:
return Element::setProperty(propertyId, v);
}
triggerLayout();
return true;
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant Clef::propertyDefault(Pid id) const
{
switch (id) {
case Pid::CLEF_TYPE_CONCERT: return int(ClefType::INVALID);
case Pid::CLEF_TYPE_TRANSPOSING: return int(ClefType::INVALID);
case Pid::SHOW_COURTESY: return true;
case Pid::SMALL: return false;
default: return Element::propertyDefault(id);
}
}
//---------------------------------------------------------
// nextSegmentElement
//---------------------------------------------------------
Element* Clef::nextSegmentElement()
{
return segment()->firstInNextSegments(staffIdx());
}
//---------------------------------------------------------
// prevSegmentElement
//---------------------------------------------------------
Element* Clef::prevSegmentElement()
{
return segment()->lastInPrevSegments(staffIdx());
}
//---------------------------------------------------------
// accessibleInfo
//---------------------------------------------------------
QString Clef::accessibleInfo() const
{
return qApp->translate("clefTable", ClefInfo::name(clefType()));
}
//---------------------------------------------------------
// clear
//---------------------------------------------------------
void Clef::clear()
{
setbbox(QRectF());
symId = SymId::noSym;
}
}