2012-05-26 14:26:10 +02:00
|
|
|
//=============================================================================
|
|
|
|
// 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 class Score (partial).
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
#include "score.h"
|
2019-06-19 16:43:37 +02:00
|
|
|
#include "fermata.h"
|
2019-03-19 11:25:33 +01:00
|
|
|
#include "imageStore.h"
|
2012-05-26 14:26:10 +02:00
|
|
|
#include "key.h"
|
|
|
|
#include "sig.h"
|
|
|
|
#include "clef.h"
|
|
|
|
#include "tempo.h"
|
|
|
|
#include "measure.h"
|
|
|
|
#include "page.h"
|
|
|
|
#include "undo.h"
|
|
|
|
#include "system.h"
|
|
|
|
#include "select.h"
|
|
|
|
#include "segment.h"
|
|
|
|
#include "xml.h"
|
|
|
|
#include "text.h"
|
|
|
|
#include "note.h"
|
|
|
|
#include "chord.h"
|
|
|
|
#include "rest.h"
|
|
|
|
#include "slur.h"
|
|
|
|
#include "staff.h"
|
|
|
|
#include "part.h"
|
|
|
|
#include "style.h"
|
|
|
|
#include "tuplet.h"
|
|
|
|
#include "lyrics.h"
|
|
|
|
#include "pitchspelling.h"
|
|
|
|
#include "line.h"
|
|
|
|
#include "volta.h"
|
|
|
|
#include "repeat.h"
|
|
|
|
#include "ottava.h"
|
|
|
|
#include "barline.h"
|
|
|
|
#include "box.h"
|
|
|
|
#include "utils.h"
|
|
|
|
#include "excerpt.h"
|
|
|
|
#include "stafftext.h"
|
|
|
|
#include "repeatlist.h"
|
|
|
|
#include "keysig.h"
|
|
|
|
#include "beam.h"
|
|
|
|
#include "stafftype.h"
|
|
|
|
#include "tempotext.h"
|
|
|
|
#include "articulation.h"
|
|
|
|
#include "revisions.h"
|
2019-05-17 20:25:11 +02:00
|
|
|
#include "tie.h"
|
2012-05-26 14:26:10 +02:00
|
|
|
#include "tiemap.h"
|
|
|
|
#include "layoutbreak.h"
|
|
|
|
#include "harmony.h"
|
|
|
|
#include "mscore.h"
|
2012-07-06 17:42:20 +02:00
|
|
|
#ifdef OMR
|
2012-05-26 14:26:10 +02:00
|
|
|
#include "omr/omr.h"
|
2012-07-06 17:42:20 +02:00
|
|
|
#endif
|
2012-05-26 14:26:10 +02:00
|
|
|
#include "bracket.h"
|
|
|
|
#include "audio.h"
|
2012-06-07 09:24:05 +02:00
|
|
|
#include "instrtemplate.h"
|
2013-11-11 16:53:03 +01:00
|
|
|
#include "sym.h"
|
2014-11-15 16:29:48 +01:00
|
|
|
#include "rehearsalmark.h"
|
2015-01-29 22:37:39 +01:00
|
|
|
#include "breath.h"
|
2015-03-05 16:46:37 +01:00
|
|
|
#include "instrchange.h"
|
2019-03-04 18:56:11 +01:00
|
|
|
#include "synthesizerstate.h"
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-05-13 18:49:17 +02:00
|
|
|
namespace Ms {
|
2016-03-10 10:41:31 +01:00
|
|
|
MasterScore* gscore; ///< system score, used for palettes etc.
|
2018-11-30 09:04:24 +01:00
|
|
|
std::set<Score*> Score::validScores;
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
bool noSeq = false;
|
|
|
|
bool noMidi = false;
|
|
|
|
bool midiInputTrace = false;
|
|
|
|
bool midiOutputTrace = false;
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// MeasureBaseList
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
MeasureBaseList::MeasureBaseList()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_first = 0;
|
|
|
|
_last = 0;
|
|
|
|
_size = 0;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// push_back
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MeasureBaseList::push_back(MeasureBase* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
++_size;
|
|
|
|
if (_last) {
|
|
|
|
_last->setNext(e);
|
|
|
|
e->setPrev(_last);
|
|
|
|
e->setNext(0);
|
|
|
|
} else {
|
|
|
|
_first = e;
|
|
|
|
e->setPrev(0);
|
|
|
|
e->setNext(0);
|
|
|
|
}
|
|
|
|
_last = e;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// push_front
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MeasureBaseList::push_front(MeasureBase* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
++_size;
|
|
|
|
if (_first) {
|
|
|
|
_first->setPrev(e);
|
|
|
|
e->setNext(_first);
|
|
|
|
e->setPrev(0);
|
|
|
|
} else {
|
|
|
|
_last = e;
|
|
|
|
e->setPrev(0);
|
|
|
|
e->setNext(0);
|
|
|
|
}
|
|
|
|
_first = e;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// add
|
|
|
|
// insert e before e->next()
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MeasureBaseList::add(MeasureBase* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
MeasureBase* el = e->next();
|
|
|
|
if (el == 0) {
|
|
|
|
push_back(e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (el == _first) {
|
|
|
|
push_front(e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
++_size;
|
|
|
|
e->setPrev(el->prev());
|
|
|
|
el->prev()->setNext(e);
|
|
|
|
el->setPrev(e);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2015-06-13 17:57:57 +02:00
|
|
|
// remove
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MeasureBaseList::remove(MeasureBase* el)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
--_size;
|
|
|
|
if (el->prev()) {
|
|
|
|
el->prev()->setNext(el->next());
|
|
|
|
} else {
|
|
|
|
_first = el->next();
|
|
|
|
}
|
|
|
|
if (el->next()) {
|
|
|
|
el->next()->setPrev(el->prev());
|
|
|
|
} else {
|
|
|
|
_last = el->prev();
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// insert
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MeasureBaseList::insert(MeasureBase* fm, MeasureBase* lm)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
++_size;
|
|
|
|
for (MeasureBase* m = fm; m != lm; m = m->next()) {
|
|
|
|
++_size;
|
|
|
|
}
|
|
|
|
MeasureBase* pm = fm->prev();
|
|
|
|
if (pm) {
|
|
|
|
pm->setNext(fm);
|
|
|
|
} else {
|
|
|
|
_first = fm;
|
|
|
|
}
|
|
|
|
MeasureBase* nm = lm->next();
|
|
|
|
if (nm) {
|
|
|
|
nm->setPrev(lm);
|
|
|
|
} else {
|
|
|
|
_last = lm;
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// remove
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MeasureBaseList::remove(MeasureBase* fm, MeasureBase* lm)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
--_size;
|
|
|
|
for (MeasureBase* m = fm; m != lm; m = m->next()) {
|
|
|
|
--_size;
|
|
|
|
}
|
|
|
|
MeasureBase* pm = fm->prev();
|
|
|
|
MeasureBase* nm = lm->next();
|
|
|
|
if (pm) {
|
|
|
|
pm->setNext(nm);
|
|
|
|
} else {
|
|
|
|
_first = nm;
|
|
|
|
}
|
|
|
|
if (nm) {
|
|
|
|
nm->setPrev(pm);
|
|
|
|
} else {
|
|
|
|
_last = pm;
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// change
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MeasureBaseList::change(MeasureBase* ob, MeasureBase* nb)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
nb->setPrev(ob->prev());
|
|
|
|
nb->setNext(ob->next());
|
|
|
|
if (ob->prev()) {
|
|
|
|
ob->prev()->setNext(nb);
|
|
|
|
}
|
|
|
|
if (ob->next()) {
|
|
|
|
ob->next()->setPrev(nb);
|
|
|
|
}
|
|
|
|
if (ob == _last) {
|
|
|
|
_last = nb;
|
|
|
|
}
|
|
|
|
if (ob == _first) {
|
|
|
|
_first = nb;
|
|
|
|
}
|
|
|
|
if (nb->type() == ElementType::HBOX || nb->type() == ElementType::VBOX
|
|
|
|
|| nb->type() == ElementType::TBOX || nb->type() == ElementType::FBOX) {
|
|
|
|
nb->setSystem(ob->system());
|
|
|
|
}
|
|
|
|
foreach (Element* e, nb->el()) {
|
|
|
|
e->setParent(nb);
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// Score
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
Score::Score() :
|
|
|
|
ScoreElement(this), _selection(this), _selectionFilter(this)
|
|
|
|
{
|
|
|
|
Score::validScores.insert(this);
|
|
|
|
_masterScore = 0;
|
|
|
|
Layer l;
|
|
|
|
l.name = "default";
|
|
|
|
l.tags = 1;
|
|
|
|
_layer.append(l);
|
|
|
|
_layerTags[0] = "default";
|
2016-03-11 12:18:46 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
_scoreFont = ScoreFont::fontFactory("emmentaler");
|
2016-03-11 12:18:46 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
_fileDivision = MScore::division;
|
|
|
|
_style = MScore::defaultStyle();
|
2017-02-17 15:09:28 +01:00
|
|
|
// accInfo = tr("No selection"); // ??
|
2020-05-26 15:54:26 +02:00
|
|
|
accInfo = "No selection";
|
|
|
|
}
|
|
|
|
|
|
|
|
Score::Score(MasterScore* parent, bool forcePartStyle /* = true */) :
|
|
|
|
Score{}
|
|
|
|
{
|
|
|
|
Score::validScores.insert(this);
|
|
|
|
_masterScore = parent;
|
|
|
|
if (MScore::defaultStyleForParts()) {
|
|
|
|
_style = *MScore::defaultStyleForParts();
|
|
|
|
} else {
|
|
|
|
// inherit most style settings from parent
|
|
|
|
_style = parent->style();
|
|
|
|
|
|
|
|
static const Sid styles[] = {
|
|
|
|
Sid::pageWidth,
|
|
|
|
Sid::pageHeight,
|
|
|
|
Sid::pagePrintableWidth,
|
|
|
|
Sid::pageEvenLeftMargin,
|
|
|
|
Sid::pageOddLeftMargin,
|
|
|
|
Sid::pageEvenTopMargin,
|
|
|
|
Sid::pageEvenBottomMargin,
|
|
|
|
Sid::pageOddTopMargin,
|
|
|
|
Sid::pageOddBottomMargin,
|
|
|
|
Sid::pageTwosided,
|
|
|
|
Sid::spatium
|
|
|
|
};
|
|
|
|
// but borrow defaultStyle page layout settings
|
|
|
|
for (auto i : styles) {
|
|
|
|
_style.set(i, MScore::defaultStyle().value(i));
|
|
|
|
}
|
|
|
|
// and force some style settings that just make sense for parts
|
|
|
|
if (forcePartStyle) {
|
|
|
|
style().set(Sid::concertPitch, false);
|
|
|
|
style().set(Sid::createMultiMeasureRests, true);
|
|
|
|
style().set(Sid::dividerLeft, false);
|
|
|
|
style().set(Sid::dividerRight, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// update style values
|
|
|
|
_style.precomputeValues();
|
|
|
|
_synthesizerState = parent->_synthesizerState;
|
|
|
|
_mscVersion = parent->_mscVersion;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
Score::Score(MasterScore* parent, const MStyle& s) :
|
|
|
|
Score{parent}
|
|
|
|
{
|
|
|
|
Score::validScores.insert(this);
|
|
|
|
_style = s;
|
|
|
|
}
|
2014-04-17 22:41:11 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// ~Score
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Score::~Score()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Score::validScores.erase(this);
|
|
|
|
|
|
|
|
foreach (MuseScoreView* v, viewer) {
|
|
|
|
v->removeScore();
|
|
|
|
}
|
|
|
|
// deselectAll();
|
|
|
|
qDeleteAll(_systems); // systems are layout-only objects so we delete
|
2018-12-02 00:45:18 +01:00
|
|
|
// them prior to measures.
|
2020-05-26 15:54:26 +02:00
|
|
|
for (MeasureBase* m = _measures.first(); m;) {
|
|
|
|
MeasureBase* nm = m->next();
|
|
|
|
if (m->isMeasure() && toMeasure(m)->mmRest()) {
|
|
|
|
delete toMeasure(m)->mmRest();
|
|
|
|
}
|
|
|
|
delete m;
|
|
|
|
m = nm;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto it = _spanner.cbegin(); it != _spanner.cend(); ++it) {
|
|
|
|
delete it->second;
|
|
|
|
}
|
|
|
|
_spanner.clear();
|
|
|
|
|
|
|
|
qDeleteAll(_parts);
|
|
|
|
qDeleteAll(_staves);
|
2018-11-05 18:51:35 +01:00
|
|
|
// qDeleteAll(_pages); // TODO: check
|
2020-05-26 15:54:26 +02:00
|
|
|
_masterScore = 0;
|
2019-03-19 11:25:33 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
imageStore.clearUnused();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2018-10-25 13:21:12 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// Score::clone
|
|
|
|
// To create excerpt clone to show when changing PageSettings
|
|
|
|
// Use MasterScore::clone() instead
|
|
|
|
//---------------------------------------------------------
|
2018-10-26 12:28:11 +02:00
|
|
|
|
2018-10-25 13:21:12 +02:00
|
|
|
Score* Score::clone()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QBuffer buffer;
|
|
|
|
buffer.open(QIODevice::WriteOnly);
|
|
|
|
XmlWriter xml(this, &buffer);
|
|
|
|
xml.header();
|
2018-10-26 12:28:11 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
xml.stag("museScore version=\"" MSC_VERSION "\"");
|
|
|
|
write(xml, false);
|
|
|
|
xml.etag();
|
2018-10-26 12:28:11 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
buffer.close();
|
2018-10-26 12:28:11 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
XmlReader r(buffer.buffer());
|
|
|
|
MasterScore* score = new MasterScore(style());
|
|
|
|
score->read1(r, true);
|
2018-10-26 12:28:11 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
score->addLayoutFlags(LayoutFlag::FIX_PITCH_VELO);
|
|
|
|
score->doLayout();
|
|
|
|
return score;
|
|
|
|
}
|
2018-10-26 12:28:11 +02:00
|
|
|
|
2018-11-30 09:04:24 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// Score::onElementDestruction
|
|
|
|
// Ensure correct state of the score after destruction
|
|
|
|
// of the element (e.g. remove invalid pointers etc.).
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::onElementDestruction(Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Score* score = e->score();
|
|
|
|
if (!score || Score::validScores.find(score) == Score::validScores.end()) {
|
|
|
|
// No score or the score is already deleted
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
score->selection().remove(e);
|
|
|
|
score->cmdState().unsetElement(e);
|
|
|
|
for (MuseScoreView* v : score->viewer) {
|
|
|
|
v->onElementDestruction(e);
|
|
|
|
}
|
|
|
|
}
|
2018-11-30 09:04:24 +01:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// addMeasure
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::addMeasure(MeasureBase* m, MeasureBase* pos)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
m->setNext(pos);
|
|
|
|
_measures.add(m);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// fixTicks
|
|
|
|
// update:
|
|
|
|
// - measure ticks
|
|
|
|
// - tempo map
|
|
|
|
// - time signature map
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
/**
|
|
|
|
This is needed after
|
2013-05-14 16:43:21 +02:00
|
|
|
- inserting or removing a measure
|
2012-05-26 14:26:10 +02:00
|
|
|
- changing the sigmap
|
|
|
|
- after inserting/deleting time (changes the sigmap)
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Score::fixTicks()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Fraction tick = Fraction(0,1);
|
|
|
|
Measure* fm = firstMeasure();
|
|
|
|
if (fm == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Staff* staff : _staves) {
|
|
|
|
staff->clearTimeSig();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isMaster()) {
|
|
|
|
tempomap()->clear();
|
|
|
|
sigmap()->clear();
|
|
|
|
sigmap()->add(0, SigEvent(fm->ticks(), fm->timesig(), 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (MeasureBase* mb = first(); mb; mb = mb->next()) {
|
|
|
|
if (mb->type() != ElementType::MEASURE) {
|
|
|
|
mb->setTick(tick);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Measure* m = toMeasure(mb);
|
|
|
|
Fraction mtick = m->tick();
|
|
|
|
Fraction diff = tick - mtick;
|
|
|
|
Fraction measureTicks = m->ticks();
|
|
|
|
m->moveTicks(diff);
|
|
|
|
if (m->mmRest()) {
|
|
|
|
m->mmRest()->moveTicks(diff);
|
|
|
|
}
|
|
|
|
|
|
|
|
rebuildTempoAndTimeSigMaps(m);
|
|
|
|
|
|
|
|
tick += measureTicks;
|
|
|
|
}
|
|
|
|
// Now done in getNextMeasure(), do we keep?
|
|
|
|
if (tempomap()->empty()) {
|
|
|
|
tempomap()->setTempo(0, _defaultTempo);
|
|
|
|
}
|
|
|
|
}
|
2019-06-19 16:43:37 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// fixTicks
|
|
|
|
/// updates tempomap and time sig map for a measure
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::rebuildTempoAndTimeSigMaps(Measure* measure)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (isMaster()) {
|
|
|
|
// Reset tempo to set correct time stretch for fermata.
|
|
|
|
const Fraction& startTick = measure->tick();
|
|
|
|
resetTempoRange(startTick, measure->endTick());
|
|
|
|
|
|
|
|
// Implement section break rest
|
|
|
|
for (MeasureBase* mb = measure->prev(); mb && mb->endTick() == startTick; mb = mb->prev()) {
|
|
|
|
if (mb->pause()) {
|
|
|
|
setPause(startTick, mb->pause());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add pauses from the end of the previous measure (at measure->tick()):
|
|
|
|
for (Segment* s = measure->first(); s && s->tick() == startTick; s = s->prev1()) {
|
|
|
|
if (!s->isBreathType()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
qreal length = 0.0;
|
|
|
|
for (Element* e : s->elist()) {
|
|
|
|
if (e && e->isBreath()) {
|
|
|
|
length = qMax(length, toBreath(e)->pause());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (length != 0.0) {
|
|
|
|
setPause(startTick, length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Segment& segment : measure->segments()) {
|
|
|
|
if (segment.isBreathType()) {
|
|
|
|
if (!isMaster()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
qreal length = 0.0;
|
|
|
|
Fraction tick = segment.tick();
|
|
|
|
// find longest pause
|
|
|
|
for (int i = 0, n = ntracks(); i < n; ++i) {
|
|
|
|
Element* e = segment.element(i);
|
|
|
|
if (e && e->isBreath()) {
|
|
|
|
Breath* b = toBreath(e);
|
|
|
|
length = qMax(length, b->pause());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (length != 0.0) {
|
|
|
|
setPause(tick, length);
|
|
|
|
}
|
|
|
|
} else if (segment.isTimeSigType()) {
|
|
|
|
for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
|
|
|
|
TimeSig* ts = toTimeSig(segment.element(staffIdx * VOICES));
|
|
|
|
if (ts) {
|
|
|
|
staff(staffIdx)->addTimeSig(ts);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (segment.isChordRestType()) {
|
|
|
|
if (!isMaster()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
qreal stretch = 0.0;
|
|
|
|
for (Element* e : segment.annotations()) {
|
|
|
|
if (e->isFermata() && toFermata(e)->play()) {
|
|
|
|
stretch = qMax(stretch, toFermata(e)->timeStretch());
|
|
|
|
} else if (e->isTempoText()) {
|
|
|
|
TempoText* tt = toTempoText(e);
|
|
|
|
if (tt->isRelative()) {
|
|
|
|
tt->updateRelative();
|
|
|
|
}
|
|
|
|
setTempo(tt->segment(), tt->tempo());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (stretch != 0.0 && stretch != 1.0) {
|
|
|
|
qreal otempo = tempomap()->tempo(segment.tick().ticks());
|
|
|
|
qreal ntempo = otempo / stretch;
|
|
|
|
setTempo(segment.tick(), ntempo);
|
|
|
|
Fraction etick = segment.tick() + segment.ticks() - Fraction(1, 480 * 4);
|
|
|
|
auto e = tempomap()->find(etick.ticks());
|
|
|
|
if (e == tempomap()->end()) {
|
|
|
|
setTempo(etick, otempo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update time signature map
|
|
|
|
// create event if measure len and time signature are different
|
|
|
|
// even if they are equivalent 4/4 vs 2/2
|
|
|
|
// also check if nominal time signature has changed
|
|
|
|
|
|
|
|
if (isMaster()) {
|
|
|
|
const Measure* m = measure;
|
|
|
|
const Fraction mTicks = m->isMMRest() ? m->mmRestFirst()->ticks() : m->ticks(); // for time signature the underlying measure length matters for MM rests
|
|
|
|
|
|
|
|
const Measure* pm = measure->prevMeasure();
|
|
|
|
// prevMeasure() doesn't return MM rest so we don't handle it here
|
|
|
|
|
|
|
|
if (pm && (!mTicks.identical(pm->ticks()) || !m->timesig().identical(pm->timesig()))) {
|
|
|
|
sigmap()->add(m->tick().ticks(), SigEvent(mTicks, m->timesig(), m->no()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// pos2measure
|
2017-05-02 14:17:31 +02:00
|
|
|
// Return measure for canvas relative position \a p.
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-05-02 14:17:31 +02:00
|
|
|
Measure* Score::pos2measure(const QPointF& p, int* rst, int* pitch, Segment** seg, QPointF* offset) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* m = searchMeasure(p);
|
|
|
|
if (m == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
System* s = m->system();
|
|
|
|
qreal y = p.y() - s->canvasPos().y();
|
|
|
|
|
|
|
|
const int i = s->searchStaff(y);
|
|
|
|
|
|
|
|
// search for segment + offset
|
|
|
|
QPointF pppp = p - m->canvasPos();
|
|
|
|
int strack = i * VOICES;
|
|
|
|
if (!staff(i)) {
|
|
|
|
return 0;
|
|
|
|
}
|
2018-04-18 09:45:20 +02:00
|
|
|
// int etrack = staff(i)->part()->nstaves() * VOICES + strack;
|
2020-05-26 15:54:26 +02:00
|
|
|
int etrack = VOICES + strack;
|
|
|
|
|
|
|
|
constexpr SegmentType st = SegmentType::ChordRest;
|
|
|
|
Segment* segment = m->searchSegment(pppp.x(), st, strack, etrack);
|
|
|
|
if (segment) {
|
|
|
|
SysStaff* sstaff = m->system()->staff(i);
|
|
|
|
*rst = i;
|
|
|
|
if (pitch) {
|
|
|
|
Staff* s1 = _staves[i];
|
|
|
|
Fraction tick = segment->tick();
|
|
|
|
ClefType clef = s1->clef(tick);
|
|
|
|
*pitch = y2pitch(pppp.y() - sstaff->bbox().y(), clef, s1->spatium(tick));
|
|
|
|
}
|
|
|
|
if (offset) {
|
|
|
|
*offset = pppp - QPointF(segment->x(), sstaff->bbox().y());
|
|
|
|
}
|
|
|
|
if (seg) {
|
|
|
|
*seg = segment;
|
|
|
|
}
|
|
|
|
return m;
|
|
|
|
}
|
2019-12-20 10:42:56 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2016-07-19 20:19:23 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// dragPosition
|
2020-01-21 14:19:45 +01:00
|
|
|
/// \param p drag position in canvas coordinates
|
|
|
|
/// \param rst \b input: current staff index \n
|
|
|
|
/// \b output: new staff index for drag position
|
|
|
|
/// \param seg \b input: current segment \n
|
|
|
|
/// \b output: new segment for drag position
|
2016-07-19 20:19:23 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2020-01-29 10:38:02 +01:00
|
|
|
void Score::dragPosition(const QPointF& p, int* rst, Segment** seg, qreal spacingFactor) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
const System* preferredSystem = (*seg) ? (*seg)->system() : nullptr;
|
|
|
|
Measure* m = searchMeasure(p, preferredSystem, spacingFactor);
|
|
|
|
if (m == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
System* s = m->system();
|
|
|
|
qreal y = p.y() - s->canvasPos().y();
|
|
|
|
|
|
|
|
const int i = s->searchStaff(y, *rst, spacingFactor);
|
|
|
|
|
|
|
|
// search for segment + offset
|
|
|
|
QPointF pppp = p - m->canvasPos();
|
|
|
|
int strack = staff2track(i);
|
|
|
|
if (!staff(i)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
int etrack = staff2track(i + 1);
|
|
|
|
|
|
|
|
constexpr SegmentType st = SegmentType::ChordRest;
|
|
|
|
Segment* segment = m->searchSegment(pppp.x(), st, strack, etrack, *seg, spacingFactor);
|
|
|
|
if (segment) {
|
|
|
|
*rst = i;
|
|
|
|
*seg = segment;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2016-07-19 20:19:23 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setShowInvisible
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setShowInvisible(bool v)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_showInvisible = v;
|
|
|
|
setUpdateAll();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setShowUnprintable
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setShowUnprintable(bool v)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_showUnprintable = v;
|
|
|
|
setUpdateAll();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setShowFrames
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setShowFrames(bool v)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_showFrames = v;
|
|
|
|
setUpdateAll();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setShowPageborders
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setShowPageborders(bool v)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_showPageborders = v;
|
|
|
|
setUpdateAll();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2016-08-06 11:36:51 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setMarkIrregularMeasures
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setMarkIrregularMeasures(bool v)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_markIrregularMeasures = v;
|
|
|
|
setUpdateAll();
|
|
|
|
}
|
2016-08-06 11:36:51 +02:00
|
|
|
|
2018-08-03 01:15:56 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// readOnly
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::readOnly() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return _masterScore->readOnly();
|
|
|
|
}
|
2018-08-03 01:15:56 +02:00
|
|
|
|
2014-02-25 14:14:59 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// dirty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::dirty() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return !undoStack()->isClean();
|
|
|
|
}
|
2014-02-25 14:14:59 +01:00
|
|
|
|
2018-07-23 19:50:53 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// state
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
ScoreContentState Score::state() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return ScoreContentState(this, undoStack()->state());
|
|
|
|
}
|
2018-07-23 19:50:53 +02:00
|
|
|
|
2019-05-23 19:23:52 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// playlistDirty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::playlistDirty() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return masterScore()->playlistDirty();
|
|
|
|
}
|
2019-05-23 19:23:52 +02:00
|
|
|
|
2019-04-19 15:16:21 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setPlaylistDirty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setPlaylistDirty()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
masterScore()->setPlaylistDirty();
|
|
|
|
}
|
2019-04-19 15:16:21 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setPlaylistDirty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MasterScore::setPlaylistDirty()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_playlistDirty = true;
|
|
|
|
_repeatList->setScoreChanged();
|
|
|
|
}
|
2019-04-19 15:16:21 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// spell
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::spell()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (int i = 0; i < nstaves(); ++i) {
|
|
|
|
std::vector<Note*> notes;
|
|
|
|
for (Segment* s = firstSegment(SegmentType::All); s; s = s->next1()) {
|
|
|
|
int strack = i * VOICES;
|
|
|
|
int etrack = strack + VOICES;
|
|
|
|
for (int track = strack; track < etrack; ++track) {
|
|
|
|
Element* e = s->element(track);
|
|
|
|
if (e && e->type() == ElementType::CHORD) {
|
|
|
|
notes.insert(notes.end(),
|
2016-06-03 10:17:06 +02:00
|
|
|
toChord(e)->notes().begin(),
|
|
|
|
toChord(e)->notes().end());
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
spellNotelist(notes);
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
void Score::spell(int startStaff, int endStaff, Segment* startSegment, Segment* endSegment)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (int i = startStaff; i < endStaff; ++i) {
|
|
|
|
std::vector<Note*> notes;
|
|
|
|
for (Segment* s = startSegment; s && s != endSegment; s = s->next()) {
|
|
|
|
int strack = i * VOICES;
|
|
|
|
int etrack = strack + VOICES;
|
|
|
|
for (int track = strack; track < etrack; ++track) {
|
|
|
|
Element* e = s->element(track);
|
|
|
|
if (e && e->type() == ElementType::CHORD) {
|
|
|
|
notes.insert(notes.end(),
|
2016-06-03 10:17:06 +02:00
|
|
|
toChord(e)->notes().begin(),
|
|
|
|
toChord(e)->notes().end());
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
spellNotelist(notes);
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// prevNote
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Note* prevNote(Note* n)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Chord* chord = n->chord();
|
|
|
|
Segment* seg = chord->segment();
|
|
|
|
const std::vector<Note*> nl = chord->notes();
|
|
|
|
auto i = std::find(nl.begin(), nl.end(), n);
|
|
|
|
if (i != nl.begin()) {
|
|
|
|
return *(i - 1);
|
|
|
|
}
|
|
|
|
int staff = n->staffIdx();
|
|
|
|
int startTrack = staff * VOICES + n->voice() - 1;
|
|
|
|
int endTrack = 0;
|
|
|
|
while (seg) {
|
|
|
|
if (seg->segmentType() == SegmentType::ChordRest) {
|
|
|
|
for (int track = startTrack; track >= endTrack; --track) {
|
|
|
|
Element* e = seg->element(track);
|
|
|
|
if (e && e->type() == ElementType::CHORD) {
|
|
|
|
return toChord(e)->upNote();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
seg = seg->prev1();
|
|
|
|
startTrack = staff * VOICES + VOICES - 1;
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// nextNote
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-02-09 09:20:54 +01:00
|
|
|
static Note* nextNote(Note* n)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Chord* chord = n->chord();
|
|
|
|
const std::vector<Note*> nl = chord->notes();
|
|
|
|
auto i = std::find(nl.begin(), nl.end(), n);
|
|
|
|
if (i != nl.end()) {
|
|
|
|
++i;
|
|
|
|
if (i != nl.end()) {
|
|
|
|
return *i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Segment* seg = chord->segment();
|
|
|
|
int staff = n->staffIdx();
|
|
|
|
int startTrack = staff * VOICES + n->voice() + 1;
|
|
|
|
int endTrack = staff * VOICES + VOICES;
|
|
|
|
while (seg) {
|
|
|
|
if (seg->segmentType() == SegmentType::ChordRest) {
|
|
|
|
for (int track = startTrack; track < endTrack; ++track) {
|
|
|
|
Element* e = seg->element(track);
|
|
|
|
if (e && e->type() == ElementType::CHORD) {
|
|
|
|
return ((Chord*)e)->downNote();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
seg = seg->next1();
|
|
|
|
startTrack = staff * VOICES;
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// spell
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::spell(Note* note)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
std::vector<Note*> notes;
|
|
|
|
|
|
|
|
notes.push_back(note);
|
|
|
|
Note* nn = nextNote(note);
|
|
|
|
notes.push_back(nn);
|
|
|
|
nn = nextNote(nn);
|
|
|
|
notes.push_back(nn);
|
|
|
|
nn = nextNote(nn);
|
|
|
|
notes.push_back(nn);
|
|
|
|
|
|
|
|
nn = prevNote(note);
|
|
|
|
notes.insert(notes.begin(), nn);
|
|
|
|
nn = prevNote(nn);
|
|
|
|
notes.insert(notes.begin(), nn);
|
|
|
|
nn = prevNote(nn);
|
|
|
|
notes.insert(notes.begin(), nn);
|
|
|
|
|
|
|
|
int opt = Ms::computeWindow(notes, 0, 7);
|
|
|
|
note->setTpc(Ms::tpc(3, note->pitch(), opt));
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// appendPart
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::appendPart(Part* p)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_parts.append(p);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// searchPage
|
|
|
|
// p is in canvas coordinates
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Page* Score::searchPage(const QPointF& p) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (Page* page : pages()) {
|
|
|
|
QRectF r = page->bbox().translated(page->pos());
|
|
|
|
if (r.contains(p)) {
|
|
|
|
return page;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// searchSystem
|
2020-01-28 21:41:13 +01:00
|
|
|
/// Returns list of systems as there may be more than
|
|
|
|
/// one system in a row
|
|
|
|
/// \param pos Position in canvas coordinates
|
|
|
|
/// \param preferredSystem If not nullptr, will give more
|
|
|
|
/// space to the given system when searching it by its
|
|
|
|
/// coordinate.
|
|
|
|
/// \returns List of found systems.
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2020-01-29 10:38:02 +01:00
|
|
|
QList<System*> Score::searchSystem(const QPointF& pos, const System* preferredSystem, qreal spacingFactor) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QList<System*> systems;
|
|
|
|
Page* page = searchPage(pos);
|
|
|
|
if (page == 0) {
|
|
|
|
return systems;
|
|
|
|
}
|
|
|
|
qreal y = pos.y() - page->pos().y(); // transform to page relative
|
|
|
|
const QList<System*>* sl = &page->systems();
|
|
|
|
qreal y2;
|
|
|
|
int n = sl->size();
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
System* s = sl->at(i);
|
|
|
|
System* ns = 0; // next system row
|
|
|
|
int ii = i + 1;
|
|
|
|
for (; ii < n; ++ii) {
|
|
|
|
ns = sl->at(ii);
|
|
|
|
if (ns->y() != s->y()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((ii == n) || (ns == 0)) {
|
|
|
|
y2 = page->height();
|
|
|
|
} else {
|
|
|
|
qreal sy2 = s->y() + s->bbox().height();
|
|
|
|
if (s == preferredSystem) {
|
|
|
|
y2 = ns->y();
|
|
|
|
} else if (ns == preferredSystem) {
|
|
|
|
y2 = sy2;
|
|
|
|
} else {
|
|
|
|
y2 = sy2 + (ns->y() - sy2) * spacingFactor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (y < y2) {
|
|
|
|
systems.append(s);
|
|
|
|
for (int iii = i + 1; ii < n; ++iii) {
|
|
|
|
if (sl->at(iii)->y() != s->y()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
systems.append(sl->at(iii));
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
return systems;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return systems;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// searchMeasure
|
2020-01-28 21:41:13 +01:00
|
|
|
/// \param p Position in canvas coordinates
|
|
|
|
/// \param preferredSystem If not nullptr, will give more
|
|
|
|
/// space to measures in this system when searching.
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2020-01-29 10:38:02 +01:00
|
|
|
Measure* Score::searchMeasure(const QPointF& p, const System* preferredSystem, qreal spacingFactor) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QList<System*> systems = searchSystem(p, preferredSystem, spacingFactor);
|
|
|
|
for (System* system : systems) {
|
|
|
|
qreal x = p.x() - system->canvasPos().x();
|
|
|
|
for (MeasureBase* mb : system->measures()) {
|
|
|
|
if (mb->isMeasure() && (x < (mb->x() + mb->bbox().width()))) {
|
|
|
|
return toMeasure(mb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// getNextValidInputSegment
|
2017-03-08 13:12:26 +01:00
|
|
|
// - s is of type SegmentType::ChordRest
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
static Segment* getNextValidInputSegment(Segment* s, int track, int voice)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (s == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
Q_ASSERT(s->segmentType() == SegmentType::ChordRest);
|
|
|
|
// Segment* s1 = s;
|
|
|
|
ChordRest* cr1 = nullptr;
|
|
|
|
for (Segment* s1 = s; s1; s1 = s1->prev(SegmentType::ChordRest)) {
|
|
|
|
cr1 = toChordRest(s1->element(track + voice));
|
|
|
|
if (cr1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Fraction nextTick = (cr1 == nullptr) ? s->measure()->tick() : cr1->tick() + cr1->actualTicks();
|
|
|
|
|
|
|
|
static const SegmentType st { SegmentType::ChordRest };
|
|
|
|
while (s) {
|
|
|
|
if (s->element(track + voice)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (voice && s->tick() == nextTick) {
|
|
|
|
return s;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
#if 0
|
2020-05-26 15:54:26 +02:00
|
|
|
int v;
|
|
|
|
for (v = 0; v < VOICES; ++v) {
|
|
|
|
if (s->element(track + v)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((v != VOICES) && voice) {
|
|
|
|
int ntick;
|
|
|
|
bool skipChord = false;
|
|
|
|
bool ns = false;
|
|
|
|
for (Segment* s1 = s->measure()->first(st); s1; s1 = s1->next(st)) {
|
|
|
|
ChordRest* cr = toChordRest(s1->element(track + voice));
|
|
|
|
if (cr) {
|
|
|
|
if (ns) {
|
|
|
|
return s1;
|
|
|
|
}
|
|
|
|
ntick = s1->tick() + cr->actualTicks();
|
|
|
|
skipChord = true;
|
|
|
|
}
|
|
|
|
if (s1 == s) {
|
|
|
|
ns = true;
|
|
|
|
}
|
|
|
|
if (skipChord) {
|
|
|
|
if (s->tick() >= ntick) {
|
|
|
|
skipChord = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!skipChord && ns) {
|
|
|
|
return s1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!skipChord) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
#endif
|
2020-05-26 15:54:26 +02:00
|
|
|
s = s->next(st);
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// getPosition
|
|
|
|
// return true if valid position found
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::getPosition(Position* pos, const QPointF& p, int voice) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* measure = searchMeasure(p);
|
|
|
|
if (measure == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
pos->fret = FRET_NONE;
|
|
|
|
//
|
|
|
|
// search staff
|
|
|
|
//
|
|
|
|
pos->staffIdx = 0;
|
|
|
|
SysStaff* sstaff = 0;
|
|
|
|
System* system = measure->system();
|
|
|
|
qreal y = p.y() - system->pagePos().y();
|
|
|
|
for (; pos->staffIdx < nstaves(); ++pos->staffIdx) {
|
|
|
|
Staff* st = staff(pos->staffIdx);
|
|
|
|
if (!st->part()->show()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
qreal sy2;
|
|
|
|
SysStaff* ss = system->staff(pos->staffIdx);
|
|
|
|
if (!ss->show()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
SysStaff* nstaff = 0;
|
|
|
|
|
|
|
|
// find next visible staff
|
|
|
|
for (int i = pos->staffIdx + 1; i < nstaves(); ++i) {
|
|
|
|
Staff* sti = staff(i);
|
|
|
|
if (!sti->part()->show()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
nstaff = system->staff(i);
|
|
|
|
if (!nstaff->show()) {
|
|
|
|
nstaff = 0;
|
|
|
|
continue;
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nstaff) {
|
|
|
|
qreal s1y2 = ss->bbox().bottom();
|
|
|
|
sy2 = system->page()->canvasPos().y() + s1y2 + (nstaff->bbox().y() - s1y2) * .5;
|
|
|
|
} else {
|
|
|
|
sy2 = system->page()->canvasPos().y() + system->page()->height() - system->pagePos().y(); // system->height();
|
|
|
|
}
|
|
|
|
if (y < sy2) {
|
|
|
|
sstaff = ss;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sstaff == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// search segment
|
|
|
|
//
|
|
|
|
QPointF pppp(p - measure->canvasPos());
|
|
|
|
qreal x = pppp.x();
|
|
|
|
Segment* segment = 0;
|
|
|
|
pos->segment = 0;
|
|
|
|
|
|
|
|
// int track = pos->staffIdx * VOICES + voice;
|
|
|
|
int track = pos->staffIdx * VOICES;
|
|
|
|
|
|
|
|
for (segment = measure->first(SegmentType::ChordRest); segment;) {
|
|
|
|
segment = getNextValidInputSegment(segment, track, voice);
|
|
|
|
if (segment == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Segment* ns = getNextValidInputSegment(segment->next(SegmentType::ChordRest), track, voice);
|
|
|
|
|
|
|
|
qreal x1 = segment->x();
|
|
|
|
qreal x2;
|
|
|
|
qreal d;
|
|
|
|
if (ns) {
|
|
|
|
x2 = ns->x();
|
|
|
|
d = x2 - x1;
|
|
|
|
} else {
|
|
|
|
x2 = measure->bbox().width();
|
|
|
|
d = (x2 - x1) * 2.0;
|
|
|
|
x = x1;
|
|
|
|
pos->segment = segment;
|
|
|
|
break;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
if (x < (x1 + d * .5)) {
|
|
|
|
x = x1;
|
|
|
|
pos->segment = segment;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
segment = ns;
|
|
|
|
}
|
|
|
|
if (segment == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// TODO: restrict to reasonable values (pitch 0-127)
|
|
|
|
//
|
|
|
|
const Staff* s = staff(pos->staffIdx);
|
|
|
|
const Fraction tick = segment->tick();
|
|
|
|
const qreal mag = s->mag(tick);
|
|
|
|
// in TABs, step from one string to another; in other staves, step on and between lines
|
|
|
|
qreal lineDist = s->staffType(tick)->lineDistance().val() * (s->isTabStaff(measure->tick()) ? 1 : .5) * mag
|
|
|
|
* spatium();
|
|
|
|
|
|
|
|
pos->line = lrint((pppp.y() - sstaff->bbox().y()) / lineDist);
|
|
|
|
if (s->isTabStaff(measure->tick())) {
|
|
|
|
if (pos->line < -1 || pos->line > s->lines(tick) + 1) {
|
2012-05-26 14:26:10 +02:00
|
|
|
return false;
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
if (pos->line < 0) {
|
|
|
|
pos->line = 0;
|
|
|
|
} else if (pos->line >= s->lines(tick)) {
|
|
|
|
pos->line = s->lines(tick) - 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
int minLine = absStep(0);
|
|
|
|
ClefType clef = s->clef(pos->segment->tick());
|
|
|
|
minLine = relStep(minLine, clef);
|
|
|
|
int maxLine = absStep(127);
|
|
|
|
maxLine = relStep(maxLine, clef);
|
|
|
|
|
|
|
|
if (pos->line > minLine || pos->line < maxLine) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
y = sstaff->y() + pos->line * lineDist;
|
|
|
|
pos->pos = QPointF(x, y) + measure->canvasPos();
|
|
|
|
return true;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// checkHasMeasures
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::checkHasMeasures() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Page* page = pages().isEmpty() ? 0 : pages().front();
|
|
|
|
const QList<System*>* sl = page ? &page->systems() : 0;
|
|
|
|
if (sl == 0 || sl->empty() || sl->front()->measures().empty()) {
|
|
|
|
qDebug("first create measure, then repeat operation");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2017-03-31 13:03:15 +02:00
|
|
|
#if 0
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// moveBracket
|
|
|
|
// columns are counted from right to left
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::moveBracket(int staffIdx, int srcCol, int dstCol)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (System* system : systems()) {
|
|
|
|
system->moveBracket(staffIdx, srcCol, dstCol);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-31 13:03:15 +02:00
|
|
|
#endif
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// spatiumHasChanged
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
static void spatiumHasChanged(void* data, Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
qreal* val = (qreal*)data;
|
|
|
|
e->spatiumChanged(val[0], val[1]);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// spatiumChanged
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::spatiumChanged(qreal oldValue, qreal newValue)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
qreal data[2];
|
|
|
|
data[0] = oldValue;
|
|
|
|
data[1] = newValue;
|
|
|
|
scanElements(data, spatiumHasChanged, true);
|
|
|
|
for (Staff* staff : _staves) {
|
|
|
|
staff->spatiumChanged(oldValue, newValue);
|
|
|
|
}
|
|
|
|
_noteHeadWidth = _scoreFont->width(SymId::noteheadBlack, newValue / SPATIUM20);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2016-09-28 10:01:20 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// updateStyle
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
static void updateStyle(void*, Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
bool v = e->generated();
|
|
|
|
e->styleChanged();
|
|
|
|
e->setGenerated(v);
|
|
|
|
}
|
2016-09-28 10:01:20 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// styleChanged
|
|
|
|
// must be called after every style change
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::styleChanged()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
scanElements(0, updateStyle);
|
|
|
|
for (int i = 0; i < MAX_HEADERS; i++) {
|
|
|
|
if (headerText(i)) {
|
|
|
|
headerText(i)->styleChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int i = 0; i < MAX_FOOTERS; i++) {
|
|
|
|
if (footerText(i)) {
|
|
|
|
footerText(i)->styleChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setLayoutAll();
|
|
|
|
}
|
2016-09-28 10:01:20 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// getCreateMeasure
|
|
|
|
// - return Measure for tick
|
|
|
|
// - create new Measure(s) if there is no measure for
|
|
|
|
// this tick
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
Measure* Score::getCreateMeasure(const Fraction& tick)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* last = lastMeasure();
|
|
|
|
if (last == 0 || ((last->tick() + last->ticks()) <= tick)) {
|
|
|
|
Fraction lastTick = last ? (last->tick() + last->ticks()) : Fraction(0,1);
|
|
|
|
while (tick >= lastTick) {
|
|
|
|
Measure* m = new Measure(this);
|
|
|
|
Fraction ts = sigmap()->timesig(lastTick).timesig();
|
|
|
|
m->setTick(lastTick);
|
|
|
|
m->setTimesig(ts);
|
|
|
|
m->setTicks(ts);
|
|
|
|
measures()->add(toMeasureBase(m));
|
|
|
|
lastTick += Fraction::fromTicks(ts.ticks());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tick2measure(tick);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// addElement
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
/**
|
|
|
|
Add \a element to its parent.
|
|
|
|
|
|
|
|
Several elements (clef, keysig, timesig) need special handling, as they may cause
|
|
|
|
changes throughout the score.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Score::addElement(Element* element)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Element* parent = element->parent();
|
|
|
|
element->triggerLayout();
|
2016-02-04 11:27:47 +01:00
|
|
|
|
2016-03-24 12:39:18 +01:00
|
|
|
// qDebug("Score(%p) Element(%p)(%s) parent %p(%s)",
|
2016-03-02 13:20:19 +01:00
|
|
|
// this, element, element->name(), parent, parent ? parent->name() : "");
|
2016-02-04 11:27:47 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
ElementType et = element->type();
|
|
|
|
if (et == ElementType::MEASURE
|
|
|
|
|| (et == ElementType::HBOX && !(parent && parent->isVBox()))
|
|
|
|
|| et == ElementType::VBOX
|
|
|
|
|| et == ElementType::TBOX
|
|
|
|
|| et == ElementType::FBOX
|
|
|
|
) {
|
|
|
|
measures()->add(toMeasureBase(element));
|
|
|
|
element->triggerLayout();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parent) {
|
|
|
|
parent->add(element);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (et) {
|
|
|
|
case ElementType::BEAM:
|
|
|
|
{
|
|
|
|
Beam* b = toBeam(element);
|
|
|
|
int n = b->elements().size();
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
b->elements().at(i)->setBeam(b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::SLUR:
|
|
|
|
addLayoutFlags(LayoutFlag::PLAY_EVENTS);
|
|
|
|
// fall through
|
|
|
|
|
|
|
|
case ElementType::VOLTA:
|
|
|
|
case ElementType::TRILL:
|
|
|
|
case ElementType::VIBRATO:
|
|
|
|
case ElementType::PEDAL:
|
|
|
|
case ElementType::TEXTLINE:
|
|
|
|
case ElementType::HAIRPIN:
|
|
|
|
case ElementType::LET_RING:
|
|
|
|
case ElementType::PALM_MUTE:
|
|
|
|
{
|
|
|
|
Spanner* spanner = toSpanner(element);
|
|
|
|
if (et == ElementType::TEXTLINE && spanner->anchor() == Spanner::Anchor::NOTE) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
addSpanner(spanner);
|
|
|
|
for (SpannerSegment* ss : spanner->spannerSegments()) {
|
|
|
|
if (ss->system()) {
|
|
|
|
ss->system()->add(ss);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::OTTAVA:
|
|
|
|
{
|
|
|
|
Ottava* o = toOttava(element);
|
|
|
|
addSpanner(o);
|
|
|
|
foreach (SpannerSegment* ss, o->spannerSegments()) {
|
|
|
|
if (ss->system()) {
|
|
|
|
ss->system()->add(ss);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cmdState().layoutFlags |= LayoutFlag::FIX_PITCH_VELO;
|
|
|
|
o->staff()->updateOttava();
|
|
|
|
setPlaylistDirty();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::DYNAMIC:
|
|
|
|
cmdState().layoutFlags |= LayoutFlag::FIX_PITCH_VELO;
|
|
|
|
setPlaylistDirty();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::TEMPO_TEXT:
|
|
|
|
fixTicks(); // rebuilds tempomap
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::INSTRUMENT_CHANGE: {
|
|
|
|
InstrumentChange* ic = toInstrumentChange(element);
|
|
|
|
ic->part()->setInstrument(ic->instrument(), ic->segment()->tick());
|
2018-07-27 21:55:07 +02:00
|
|
|
#if 0
|
2020-05-26 15:54:26 +02:00
|
|
|
int tickStart = ic->segment()->tick();
|
|
|
|
auto i = ic->part()->instruments()->upper_bound(tickStart);
|
|
|
|
int tickEnd;
|
|
|
|
if (i == ic->part()->instruments()->end()) {
|
|
|
|
tickEnd = -1;
|
|
|
|
} else {
|
|
|
|
tickEnd = i->first;
|
|
|
|
}
|
|
|
|
Interval oldV = ic->part()->instrument(tickStart)->transpose();
|
|
|
|
ic->part()->setInstrument(ic->instrument(), tickStart);
|
|
|
|
transpositionChanged(ic->part(), oldV, tickStart, tickEnd);
|
2018-07-27 21:55:07 +02:00
|
|
|
#endif
|
2020-05-26 15:54:26 +02:00
|
|
|
addLayoutFlags(LayoutFlag::REBUILD_MIDI_MAPPING);
|
|
|
|
cmdState()._instrumentsChanged = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::CHORD:
|
|
|
|
setPlaylistDirty();
|
|
|
|
// create playlist does not work here bc. tremolos may not be complete
|
|
|
|
// createPlayEvents(toChord(element));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::NOTE:
|
|
|
|
case ElementType::TREMOLO:
|
|
|
|
case ElementType::ARTICULATION:
|
|
|
|
case ElementType::ARPEGGIO:
|
|
|
|
{
|
|
|
|
Element* cr = parent;
|
|
|
|
if (cr->isChord()) {
|
|
|
|
createPlayEvents(toChord(cr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ElementType::HARMONY:
|
|
|
|
element->part()->updateHarmonyChannels(true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
element->triggerLayout();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// removeElement
|
|
|
|
/// Remove \a element from its parent.
|
|
|
|
/// Several elements (clef, keysig, timesig) need special handling, as they may cause
|
|
|
|
/// changes throughout the score.
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::removeElement(Element* element)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Element* parent = element->parent();
|
|
|
|
element->triggerLayout();
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2016-03-24 12:39:18 +01:00
|
|
|
// qDebug("Score(%p) Element(%p)(%s) parent %p(%s)",
|
2016-03-02 13:20:19 +01:00
|
|
|
// this, element, element->name(), parent, parent ? parent->name() : "");
|
2014-05-21 20:08:37 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
// special for MEASURE, HBOX, VBOX
|
|
|
|
// their parent is not static
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
ElementType et = element->type();
|
|
|
|
|
|
|
|
if (et == ElementType::MEASURE
|
|
|
|
|| (et == ElementType::HBOX && !parent->isVBox())
|
|
|
|
|| et == ElementType::VBOX
|
|
|
|
|| et == ElementType::TBOX
|
|
|
|
|| et == ElementType::FBOX
|
|
|
|
) {
|
|
|
|
MeasureBase* mb = toMeasureBase(element);
|
|
|
|
measures()->remove(mb);
|
|
|
|
System* system = mb->system();
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
if (!system) { // vertical boxes are not shown in continuous view so no system
|
|
|
|
Q_ASSERT(lineMode() && (element->isVBox() || element->isTBox()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Page* page = system->page();
|
|
|
|
if (element->isBox() && system->measures().size() == 1) {
|
|
|
|
auto i = std::find(page->systems().begin(), page->systems().end(), system);
|
|
|
|
page->systems().erase(i);
|
|
|
|
mb->setSystem(0);
|
|
|
|
if (page->systems().isEmpty()) {
|
|
|
|
// Remove this page, since it is now empty.
|
|
|
|
// This involves renumbering and repositioning all subsequent pages.
|
|
|
|
QPointF pos = page->pos();
|
|
|
|
auto ii = std::find(pages().begin(), pages().end(), page);
|
|
|
|
pages().erase(ii);
|
|
|
|
while (ii != pages().end()) {
|
|
|
|
page = *ii;
|
|
|
|
page->setNo(page->no() - 1);
|
|
|
|
QPointF p = page->pos();
|
|
|
|
page->setPos(pos);
|
|
|
|
pos = p;
|
|
|
|
ii++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// setLayout(mb->tick());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (et == ElementType::BEAM) { // beam parent does not survive layout
|
|
|
|
element->setParent(0);
|
|
|
|
parent = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parent) {
|
|
|
|
parent->remove(element);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (et) {
|
|
|
|
case ElementType::BEAM:
|
|
|
|
for (ChordRest* cr : toBeam(element)->elements()) {
|
|
|
|
cr->setBeam(0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::SLUR:
|
|
|
|
addLayoutFlags(LayoutFlag::PLAY_EVENTS);
|
|
|
|
// fall through
|
|
|
|
|
|
|
|
case ElementType::VOLTA:
|
|
|
|
case ElementType::TRILL:
|
|
|
|
case ElementType::VIBRATO:
|
|
|
|
case ElementType::PEDAL:
|
|
|
|
case ElementType::LET_RING:
|
|
|
|
case ElementType::PALM_MUTE:
|
|
|
|
case ElementType::TEXTLINE:
|
|
|
|
case ElementType::HAIRPIN:
|
|
|
|
{
|
|
|
|
Spanner* spanner = toSpanner(element);
|
|
|
|
if (et == ElementType::TEXTLINE && spanner->anchor() == Spanner::Anchor::NOTE) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
spanner->triggerLayout();
|
|
|
|
removeSpanner(spanner);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::OTTAVA:
|
|
|
|
{
|
|
|
|
Ottava* o = toOttava(element);
|
|
|
|
o->triggerLayout();
|
|
|
|
removeSpanner(o);
|
|
|
|
o->staff()->updateOttava();
|
|
|
|
cmdState().layoutFlags |= LayoutFlag::FIX_PITCH_VELO;
|
|
|
|
setPlaylistDirty();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::DYNAMIC:
|
|
|
|
cmdState().layoutFlags |= LayoutFlag::FIX_PITCH_VELO;
|
|
|
|
setPlaylistDirty();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::CHORD:
|
|
|
|
case ElementType::REST:
|
|
|
|
{
|
|
|
|
ChordRest* cr = toChordRest(element);
|
|
|
|
if (cr->beam()) {
|
|
|
|
cr->beam()->remove(cr);
|
|
|
|
}
|
|
|
|
for (Lyrics* lyr : cr->lyrics()) {
|
|
|
|
lyr->removeFromScore();
|
|
|
|
}
|
|
|
|
// TODO: check for tuplet?
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ElementType::TEMPO_TEXT:
|
|
|
|
fixTicks(); // rebuilds tempomap
|
|
|
|
break;
|
|
|
|
case ElementType::INSTRUMENT_CHANGE: {
|
|
|
|
InstrumentChange* ic = toInstrumentChange(element);
|
|
|
|
ic->part()->removeInstrument(ic->segment()->tick());
|
2018-07-27 21:55:07 +02:00
|
|
|
#if 0
|
2020-05-26 15:54:26 +02:00
|
|
|
int tickStart = ic->segment()->tick();
|
|
|
|
auto i = ic->part()->instruments()->upper_bound(tickStart);
|
|
|
|
int tickEnd;
|
|
|
|
if (i == ic->part()->instruments()->end()) {
|
|
|
|
tickEnd = -1;
|
|
|
|
} else {
|
|
|
|
tickEnd = i->first;
|
|
|
|
}
|
|
|
|
Interval oldV = ic->part()->instrument(tickStart)->transpose();
|
|
|
|
ic->part()->removeInstrument(tickStart);
|
|
|
|
transpositionChanged(ic->part(), oldV, tickStart, tickEnd);
|
2018-07-27 21:55:07 +02:00
|
|
|
#endif
|
2020-05-26 15:54:26 +02:00
|
|
|
addLayoutFlags(LayoutFlag::REBUILD_MIDI_MAPPING);
|
|
|
|
cmdState()._instrumentsChanged = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ElementType::TREMOLO:
|
|
|
|
case ElementType::ARTICULATION:
|
|
|
|
case ElementType::ARPEGGIO:
|
|
|
|
{
|
|
|
|
Element* cr = element->parent();
|
|
|
|
if (cr->isChord()) {
|
|
|
|
createPlayEvents(toChord(cr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ElementType::HARMONY:
|
|
|
|
element->part()->updateHarmonyChannels(true, true);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// firstMeasure
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Measure* Score::firstMeasure() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
MeasureBase* mb = _measures.first();
|
|
|
|
while (mb && mb->type() != ElementType::MEASURE) {
|
|
|
|
mb = mb->next();
|
|
|
|
}
|
2013-09-19 15:08:54 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
return toMeasure(mb);
|
|
|
|
}
|
2013-09-19 15:08:54 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// firstMeasureMM
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Measure* Score::firstMeasureMM() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* m = firstMeasure();
|
|
|
|
if (m && styleB(Sid::createMultiMeasureRests) && m->hasMMRest()) {
|
|
|
|
return m->mmRest();
|
|
|
|
}
|
|
|
|
return m;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-09-19 15:08:54 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// firstMM
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
MeasureBase* Score::firstMM() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
MeasureBase* m = _measures.first();
|
|
|
|
if (m
|
|
|
|
&& m->type() == ElementType::MEASURE
|
|
|
|
&& styleB(Sid::createMultiMeasureRests)
|
|
|
|
&& toMeasure(m)->hasMMRest()) {
|
|
|
|
return toMeasure(m)->mmRest();
|
|
|
|
}
|
|
|
|
return m;
|
|
|
|
}
|
2013-09-19 15:08:54 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// measure
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
MeasureBase* Score::measure(int idx) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
MeasureBase* mb = _measures.first();
|
|
|
|
for (int i = 0; i < idx; ++i) {
|
|
|
|
mb = mb->next();
|
|
|
|
if (mb == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mb;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2018-07-07 13:15:02 +02:00
|
|
|
// crMeasure
|
|
|
|
// Returns a measure containing chords an rests
|
|
|
|
// by its index skipping other MeasureBase descendants
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Measure* Score::crMeasure(int idx) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int i = -1;
|
|
|
|
for (MeasureBase* mb = _measures.first(); mb; mb = mb->next()) {
|
|
|
|
if (mb->isMeasure()) {
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
if (i == idx) {
|
|
|
|
return toMeasure(mb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
2018-07-07 13:15:02 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2012-05-26 14:26:10 +02:00
|
|
|
// lastMeasure
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Measure* Score::lastMeasure() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
MeasureBase* mb = _measures.last();
|
|
|
|
while (mb && mb->type() != ElementType::MEASURE) {
|
|
|
|
mb = mb->prev();
|
|
|
|
}
|
|
|
|
return toMeasure(mb);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-10-06 16:43:43 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// lastMeasureMM
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Measure* Score::lastMeasureMM() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* m = lastMeasure();
|
|
|
|
if (m && styleB(Sid::createMultiMeasureRests)) {
|
|
|
|
Measure* m1 = const_cast<Measure*>(toMeasure(m->mmRest1()));
|
|
|
|
if (m1) {
|
|
|
|
return m1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m;
|
|
|
|
}
|
2013-10-06 16:43:43 +02:00
|
|
|
|
2017-02-13 17:06:30 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// endTick
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction Score::endTick() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* m = lastMeasure();
|
|
|
|
return m ? m->endTick() : Fraction(0,1);
|
|
|
|
}
|
2017-02-13 17:06:30 +01:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// firstSegment
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-03-08 13:12:26 +01:00
|
|
|
Segment* Score::firstSegment(SegmentType segType) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Segment* seg;
|
|
|
|
Measure* m = firstMeasure();
|
|
|
|
if (!m) {
|
|
|
|
seg = 0;
|
|
|
|
} else {
|
|
|
|
seg = m->first();
|
|
|
|
if (seg && !(seg->segmentType() & segType)) {
|
|
|
|
seg = seg->next1(segType);
|
|
|
|
}
|
|
|
|
}
|
2014-12-15 13:31:59 +01:00
|
|
|
|
2014-07-06 01:56:30 +02:00
|
|
|
#ifdef SCRIPT_INTERFACE
|
2020-05-26 15:54:26 +02:00
|
|
|
// if called from QML/JS, tell QML engine not to garbage collect this object
|
2017-02-17 15:09:28 +01:00
|
|
|
// if (seg)
|
|
|
|
// QQmlEngine::setObjectOwnership(seg, QQmlEngine::CppOwnership);
|
2014-07-06 01:56:30 +02:00
|
|
|
#endif
|
2020-05-26 15:54:26 +02:00
|
|
|
return seg;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-10-02 10:26:09 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// firstSegmentMM
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-03-08 13:12:26 +01:00
|
|
|
Segment* Score::firstSegmentMM(SegmentType segType) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* m = firstMeasureMM();
|
|
|
|
return m ? m->first(segType) : 0;
|
|
|
|
}
|
2013-10-02 10:26:09 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// lastSegment
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Segment* Score::lastSegment() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* m = lastMeasure();
|
|
|
|
return m ? m->last() : 0;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-02-21 20:23:26 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// lastSegmentMM
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
Segment* Score::lastSegmentMM() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* m = lastMeasureMM();
|
|
|
|
return m ? m->last() : 0;
|
|
|
|
}
|
2020-02-21 20:23:26 +01:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// utick2utime
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
qreal Score::utick2utime(int tick) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return repeatList().utick2utime(tick);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// utime2utick
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Score::utime2utick(qreal utime) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return repeatList().utime2utick(utime);
|
|
|
|
}
|
2019-04-19 15:16:21 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setExpandRepeats
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MasterScore::setExpandRepeats(bool expand)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (_expandRepeats == expand) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_expandRepeats = expand;
|
|
|
|
setPlaylistDirty();
|
|
|
|
}
|
2019-04-19 15:16:21 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// updateRepeatListTempo
|
|
|
|
/// needed for usage in Seq::processMessages
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MasterScore::updateRepeatListTempo()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_repeatList->updateTempo();
|
|
|
|
}
|
2019-04-19 15:16:21 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// repeatList
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
const RepeatList& MasterScore::repeatList() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_repeatList->update(_expandRepeats);
|
|
|
|
return *_repeatList;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// inputPos
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
Fraction Score::inputPos() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return _is.tick();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// scanElements
|
|
|
|
// scan all elements
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
void Score::scanElements(void* data, void (* func)(void*, Element*), bool all)
|
|
|
|
{
|
|
|
|
for (MeasureBase* mb = first(); mb; mb = mb->next()) {
|
|
|
|
mb->scanElements(data, func, all);
|
|
|
|
if (mb->type() == ElementType::MEASURE) {
|
|
|
|
Measure* m = toMeasure(mb);
|
|
|
|
Measure* mmr = m->mmRest();
|
|
|
|
if (mmr) {
|
|
|
|
mmr->scanElements(data, func, all);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (Page* page : pages()) {
|
|
|
|
for (System* s :page->systems()) {
|
|
|
|
s->scanElements(data, func, all);
|
|
|
|
}
|
|
|
|
func(data, page);
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2014-12-04 14:40:26 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// scanElementsInRange
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
void Score::scanElementsInRange(void* data, void (* func)(void*, Element*), bool all)
|
|
|
|
{
|
|
|
|
Segment* startSeg = _selection.startSegment();
|
|
|
|
for (Segment* s = startSeg; s && s != _selection.endSegment(); s = s->next1()) {
|
|
|
|
s->scanElements(data, func, all);
|
|
|
|
Measure* m = s->measure();
|
|
|
|
if (m && s == m->first()) {
|
|
|
|
Measure* mmr = m->mmRest();
|
|
|
|
if (mmr) {
|
|
|
|
mmr->scanElements(data, func, all);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (Element* e : _selection.elements()) {
|
|
|
|
if (e->isSpanner()) {
|
|
|
|
Spanner* spanner = toSpanner(e);
|
|
|
|
for (SpannerSegment* ss : spanner->spannerSegments()) {
|
|
|
|
ss->scanElements(data, func, all);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-15 12:55:33 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setSelection
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setSelection(const Selection& s)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
deselectAll();
|
|
|
|
_selection = s;
|
2013-04-17 10:31:21 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
foreach (Element* e, _selection.elements()) {
|
|
|
|
e->setSelected(true);
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// getText
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-08-01 11:46:07 +02:00
|
|
|
Text* Score::getText(Tid tid)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
MeasureBase* m = first();
|
|
|
|
if (m && m->isVBox()) {
|
|
|
|
for (Element* e : m->el()) {
|
|
|
|
if (e->isText() && toText(e)->tid() == tid) {
|
|
|
|
return toText(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-07-19 10:39:32 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// metaTag
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-05-22 19:49:51 +02:00
|
|
|
QString Score::metaTag(const QString& s) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (_metaTags.contains(s)) {
|
|
|
|
return _metaTags.value(s);
|
|
|
|
}
|
|
|
|
return _masterScore->_metaTags.value(s);
|
|
|
|
}
|
2013-07-19 10:39:32 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setMetaTag
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-05-22 19:49:51 +02:00
|
|
|
void Score::setMetaTag(const QString& tag, const QString& val)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_metaTags.insert(tag, val);
|
|
|
|
}
|
2013-07-19 10:39:32 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// addExcerpt
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-10-10 18:37:28 +02:00
|
|
|
void MasterScore::addExcerpt(Excerpt* ex)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Score* score = ex->partScore();
|
|
|
|
|
|
|
|
for (Staff* s : score->staves()) {
|
|
|
|
const LinkedElements* ls = s->links();
|
|
|
|
if (ls == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for (auto le : *ls) {
|
|
|
|
Staff* ps = toStaff(le);
|
|
|
|
if (ps->score() == this) {
|
|
|
|
ex->parts().append(ps->part());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ex->tracks().isEmpty()) { // SHOULDN'T HAPPEN, protected in the UI
|
|
|
|
QMultiMap<int, int> tracks;
|
|
|
|
for (Staff* s : score->staves()) {
|
2018-04-27 13:29:20 +02:00
|
|
|
const LinkedElements* ls = s->links();
|
2020-05-26 15:54:26 +02:00
|
|
|
if (ls == 0) {
|
|
|
|
continue;
|
2016-07-31 15:23:11 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
for (auto le : *ls) {
|
|
|
|
Staff* ps = toStaff(le);
|
|
|
|
if (ps->primaryStaff()) {
|
|
|
|
for (int i = 0; i < VOICES; i++) {
|
|
|
|
tracks.insert(ps->idx() * VOICES + i % VOICES, s->idx() * VOICES + i % VOICES);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ex->setTracks(tracks);
|
|
|
|
}
|
|
|
|
excerpts().append(ex);
|
|
|
|
setExcerptsChanged(true);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// removeExcerpt
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-10-10 18:37:28 +02:00
|
|
|
void MasterScore::removeExcerpt(Excerpt* ex)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (excerpts().removeOne(ex)) {
|
|
|
|
setExcerptsChanged(true);
|
|
|
|
// delete ex;
|
|
|
|
} else {
|
|
|
|
qDebug("removeExcerpt:: ex not found");
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// clone
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-03-10 10:41:31 +01:00
|
|
|
MasterScore* MasterScore::clone()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QBuffer buffer;
|
|
|
|
buffer.open(QIODevice::WriteOnly);
|
|
|
|
XmlWriter xml(this, &buffer);
|
|
|
|
xml.header();
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
xml.stag("museScore version=\"" MSC_VERSION "\"");
|
|
|
|
write(xml, false);
|
|
|
|
xml.etag();
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
buffer.close();
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
XmlReader r(buffer.buffer());
|
|
|
|
MasterScore* score = new MasterScore(style());
|
|
|
|
score->read1(r, true);
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
score->addLayoutFlags(LayoutFlag::FIX_PITCH_VELO);
|
|
|
|
score->doLayout();
|
|
|
|
return score;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2013-04-02 20:46:07 +02:00
|
|
|
// setSynthesizerState
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2013-04-02 20:46:07 +02:00
|
|
|
void Score::setSynthesizerState(const SynthesizerState& s)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
// TODO: make undoable
|
|
|
|
_synthesizerState = s;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// removeAudio
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::removeAudio()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
delete _audio;
|
|
|
|
_audio = 0;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// appendScore
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-04-12 00:18:41 +02:00
|
|
|
bool Score::appendScore(Score* score, bool addPageBreak, bool addSectionBreak)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (parts().size() < score->parts().size() || staves().size() < score->staves().size()) {
|
|
|
|
qDebug("Score to append has %d parts and %d staves, but this score only has %d parts and %d staves.",
|
|
|
|
score->parts().size(), score->staves().size(), parts().size(), staves().size());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!last()) {
|
|
|
|
qDebug("This score doesn't have any MeasureBase objects.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// apply Page/Section Breaks if desired
|
|
|
|
if (addPageBreak) {
|
|
|
|
if (!last()->pageBreak()) {
|
|
|
|
last()->undoSetBreak(false, LayoutBreak::Type::LINE); // remove line break if exists
|
|
|
|
last()->undoSetBreak(true, LayoutBreak::Type::PAGE); // apply page break
|
|
|
|
}
|
|
|
|
} else if (!last()->lineBreak() && !last()->pageBreak()) {
|
|
|
|
last()->undoSetBreak(true, LayoutBreak::Type::LINE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (addSectionBreak && !last()->sectionBreak()) {
|
|
|
|
last()->undoSetBreak(true, LayoutBreak::Type::SECTION);
|
|
|
|
}
|
|
|
|
|
|
|
|
// match concert pitch states
|
|
|
|
if (styleB(Sid::concertPitch) != score->styleB(Sid::concertPitch)) {
|
|
|
|
score->cmdConcertPitchChanged(styleB(Sid::concertPitch), true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// clone the measures
|
|
|
|
appendMeasuresFromScore(score, Fraction(0, 1), score->last()->endTick());
|
|
|
|
|
|
|
|
setLayoutAll();
|
|
|
|
return true;
|
|
|
|
}
|
2019-02-21 01:30:35 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// appendMeasuresFromScore
|
|
|
|
// clone measures from another score to the end of this
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::appendMeasuresFromScore(Score* score, const Fraction& startTick, const Fraction& endTick)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Fraction tickOfAppend = last()->endTick();
|
|
|
|
MeasureBase* pmb = last();
|
|
|
|
TieMap tieMap;
|
|
|
|
|
|
|
|
MeasureBase* fmb = score->tick2measureBase(startTick);
|
|
|
|
MeasureBase* emb = score->tick2measureBase(endTick);
|
|
|
|
Fraction curTick = tickOfAppend;
|
|
|
|
for (MeasureBase* cmb = fmb; cmb != emb; cmb = cmb->next()) {
|
|
|
|
MeasureBase* nmb;
|
|
|
|
if (cmb->isMeasure()) {
|
|
|
|
Measure* nm = toMeasure(cmb)->cloneMeasure(this, curTick, &tieMap);
|
|
|
|
curTick += nm->ticks();
|
|
|
|
nmb = toMeasureBase(nm);
|
|
|
|
} else {
|
|
|
|
nmb = cmb->clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
addMeasure(nmb, 0);
|
|
|
|
nmb->setNext(0);
|
|
|
|
nmb->setPrev(pmb);
|
|
|
|
nmb->setScore(this);
|
|
|
|
|
|
|
|
pmb->setNext(nmb);
|
|
|
|
pmb = nmb;
|
|
|
|
}
|
|
|
|
|
|
|
|
Measure* firstAppendedMeasure = tick2measure(tickOfAppend);
|
|
|
|
|
|
|
|
// if the appended score has less staves,
|
|
|
|
// make sure the measures have full measure rest
|
|
|
|
for (Measure* m = firstAppendedMeasure; m; m = m->nextMeasure()) {
|
|
|
|
for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) {
|
|
|
|
Fraction f;
|
|
|
|
for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
|
|
|
|
for (int v = 0; v < VOICES; ++v) {
|
|
|
|
ChordRest* cr = toChordRest(s->element(staffIdx * VOICES + v));
|
|
|
|
if (cr == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
f += cr->actualTicks();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (f.isZero()) {
|
|
|
|
addRest(m->tick(), staffIdx * VOICES, TDuration(TDuration::DurationType::V_MEASURE), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// at first added measure, check if we need to add Clef/Key/TimeSig
|
|
|
|
// this is needed if it was changed and needs to be changed back
|
|
|
|
int n = nstaves();
|
|
|
|
Fraction otick = fmb->tick(), ctick = tickOfAppend;
|
|
|
|
for (int staffIdx = 0; staffIdx < n; ++staffIdx) { // iterate over all staves
|
|
|
|
int trackIdx = staff2track(staffIdx); // idx of irst track on the staff
|
|
|
|
Staff* staff = this->staff(staffIdx);
|
|
|
|
Staff* ostaff = score->staff(staffIdx);
|
|
|
|
|
|
|
|
// check if key signature needs to be changed
|
|
|
|
if (ostaff->key(otick) != staff->key(ctick)) {
|
|
|
|
Segment* ns = firstAppendedMeasure->undoGetSegment(SegmentType::KeySig, ctick);
|
|
|
|
KeySigEvent nkse = KeySigEvent(ostaff->keySigEvent(otick));
|
|
|
|
KeySig* nks = new KeySig(this);
|
|
|
|
nks->setScore(this);
|
|
|
|
nks->setTrack(trackIdx);
|
|
|
|
|
|
|
|
nks->setKeySigEvent(nkse);
|
|
|
|
staff->setKey(ctick, nkse);
|
|
|
|
ns->add(nks);
|
|
|
|
}
|
|
|
|
// check if a key signature is present but is spurious (i.e. no actual change)
|
|
|
|
else if (staff->currentKeyTick(ctick) == ctick
|
|
|
|
&& staff->key(ctick - Fraction::fromTicks(1)) == ostaff->key(otick)) {
|
|
|
|
Segment* ns = firstAppendedMeasure->first(SegmentType::KeySig);
|
|
|
|
if (ns) {
|
|
|
|
ns->remove(ns->element(trackIdx));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if time signature needs to be changed
|
|
|
|
TimeSig* ots = ostaff->timeSig(otick), * cts = staff->timeSig(ctick);
|
|
|
|
TimeSig* pts = staff->timeSig(ctick - Fraction::fromTicks(1));
|
|
|
|
if (ots && cts && *ots != *cts) {
|
|
|
|
Segment* ns = firstAppendedMeasure->undoGetSegment(SegmentType::TimeSig, ctick);
|
|
|
|
TimeSig* nsig = new TimeSig(*ots);
|
|
|
|
|
|
|
|
nsig->setScore(this);
|
|
|
|
nsig->setTrack(trackIdx);
|
|
|
|
ns->add(nsig);
|
|
|
|
}
|
|
|
|
// check if a time signature is present but is spurious (i.e. no actual change)
|
|
|
|
else if (staff->currentTimeSigTick(ctick) == ctick
|
|
|
|
&& ots && pts && *pts == *ots) {
|
|
|
|
Segment* ns = firstAppendedMeasure->first(SegmentType::TimeSig);
|
|
|
|
if (ns) {
|
|
|
|
ns->remove(ns->element(trackIdx));
|
2014-11-28 14:49:46 +01:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
2014-11-28 14:49:46 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
// check if clef signature needs to be changed
|
|
|
|
if (ostaff->clef(otick) != staff->clef(ctick)) {
|
|
|
|
undoChangeClef(staff, firstAppendedMeasure, ostaff->clef(otick));
|
|
|
|
}
|
|
|
|
// check if a clef change is present but is spurious (i.e. no actual change)
|
|
|
|
else if (staff->currentClefTick(ctick) == ctick
|
|
|
|
&& staff->clef(ctick - Fraction::fromTicks(1)) == ostaff->clef(otick)) {
|
|
|
|
Segment* ns = firstAppendedMeasure->first(SegmentType::Clef);
|
|
|
|
if (!ns) {
|
|
|
|
ns = firstAppendedMeasure->first(SegmentType::HeaderClef);
|
2014-11-28 14:49:46 +01:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
if (ns) {
|
|
|
|
ns->remove(ns->element(trackIdx));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if section starts with a pick-up measure to be merged with end of previous section
|
|
|
|
Measure* cm = firstAppendedMeasure, * pm = cm->prevMeasure();
|
|
|
|
if (pm->timesig() == cm->timesig() && pm->ticks() + cm->ticks() == cm->timesig()) {
|
|
|
|
cmdJoinMeasure(pm, cm);
|
|
|
|
}
|
|
|
|
|
|
|
|
// clone the spanners (only in the range currently copied)
|
|
|
|
auto ospans = score->spanner();
|
|
|
|
auto lb = ospans.lower_bound(startTick.ticks()), ub = ospans.upper_bound(endTick.ticks());
|
|
|
|
for (auto sp = lb; sp != ub; sp++) {
|
|
|
|
Spanner* spanner = sp->second;
|
|
|
|
|
|
|
|
if (spanner->tick2() > endTick) {
|
|
|
|
continue; // map is by tick() so this can still happen in theory...
|
|
|
|
}
|
|
|
|
Spanner* ns = toSpanner(spanner->clone());
|
|
|
|
ns->setScore(this);
|
|
|
|
ns->setParent(0);
|
|
|
|
ns->setTick(spanner->tick() - startTick + tickOfAppend);
|
|
|
|
ns->setTick2(spanner->tick2() - startTick + tickOfAppend);
|
|
|
|
ns->computeStartElement();
|
|
|
|
ns->computeEndElement();
|
|
|
|
addElement(ns);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// splitStaff
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::splitStaff(int staffIdx, int splitPoint)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
2016-04-11 15:28:32 +02:00
|
|
|
// qDebug("split staff %d point %d", staffIdx, splitPoint);
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
//
|
|
|
|
// create second staff
|
|
|
|
//
|
|
|
|
Staff* st = staff(staffIdx);
|
|
|
|
Part* p = st->part();
|
|
|
|
Staff* ns = new Staff(this);
|
|
|
|
ns->init(st);
|
|
|
|
ns->setPart(p);
|
|
|
|
// convert staffIdx from score-relative to part-relative
|
|
|
|
int staffIdxPart = staffIdx - p->staff(0)->idx();
|
|
|
|
undoInsertStaff(ns, staffIdxPart + 1, false);
|
|
|
|
|
|
|
|
Clef* clef = new Clef(this);
|
|
|
|
clef->setClefType(ClefType::F);
|
|
|
|
clef->setTrack((staffIdx + 1) * VOICES);
|
|
|
|
Segment* seg = firstMeasure()->getSegment(SegmentType::HeaderClef, Fraction(0, 1));
|
|
|
|
clef->setParent(seg);
|
|
|
|
undoAddElement(clef);
|
|
|
|
clef->layout();
|
|
|
|
|
|
|
|
undoChangeKeySig(ns, Fraction(0, 1), st->keySigEvent(Fraction(0, 1)));
|
|
|
|
|
|
|
|
masterScore()->rebuildMidiMapping();
|
|
|
|
cmdState()._instrumentsChanged = true;
|
|
|
|
doLayout();
|
|
|
|
|
|
|
|
//
|
|
|
|
// move notes
|
|
|
|
//
|
|
|
|
select(0, SelectType::SINGLE, 0);
|
|
|
|
int strack = staffIdx * VOICES;
|
|
|
|
int dtrack = (staffIdx + 1) * VOICES;
|
|
|
|
|
|
|
|
// Keep track of ties to be reconnected.
|
|
|
|
struct OldTie {
|
|
|
|
Tie* tie;
|
|
|
|
Note* nnote;
|
|
|
|
};
|
|
|
|
QMap<Note*, OldTie> oldTies;
|
|
|
|
|
|
|
|
// Notes under the split point can be part of a tuplet, so keep track
|
|
|
|
// of the tuplet mapping too!
|
|
|
|
QMap<Tuplet*, Tuplet*> tupletMapping;
|
|
|
|
Tuplet* tupletSrc[VOICES] = { };
|
|
|
|
Tuplet* tupletDst[VOICES] = { };
|
|
|
|
|
|
|
|
for (Segment* s = firstSegment(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
|
|
|
|
for (int voice = 0; voice < VOICES; ++voice) {
|
|
|
|
Element* e = s->element(strack + voice);
|
|
|
|
|
|
|
|
if (!e) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (toDurationElement(e)->tuplet()) {
|
|
|
|
tupletSrc[voice] = toDurationElement(e)->tuplet();
|
|
|
|
if (tupletMapping.contains(tupletSrc[voice])) {
|
|
|
|
tupletDst[voice] = tupletMapping[tupletSrc[voice]];
|
|
|
|
} else {
|
|
|
|
tupletDst[voice] = new Tuplet(*tupletSrc[voice]);
|
|
|
|
tupletDst[voice]->setTrack(dtrack);
|
|
|
|
tupletMapping.insert(tupletSrc[voice], tupletDst[voice]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
tupletSrc[voice] = nullptr;
|
|
|
|
tupletDst[voice] = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool createRestDst = true;
|
|
|
|
bool createRestSrc = false;
|
|
|
|
TDuration lengthDst = toChordRest(e)->actualDurationType();
|
|
|
|
TDuration lengthSrc = TDuration();
|
|
|
|
|
|
|
|
if (e->isChord()) {
|
|
|
|
Chord* c = toChord(e);
|
|
|
|
QList<Note*> removeNotes;
|
|
|
|
for (Note* note : c->notes()) {
|
|
|
|
if (note->pitch() >= splitPoint) {
|
2012-05-26 14:26:10 +02:00
|
|
|
continue;
|
2020-05-26 15:54:26 +02:00
|
|
|
} else {
|
|
|
|
Chord* chord = toChord(s->element(dtrack + voice));
|
|
|
|
Q_ASSERT(!chord || (chord->isChord()));
|
|
|
|
if (!chord) {
|
|
|
|
chord = new Chord(*c);
|
|
|
|
qDeleteAll(chord->notes());
|
|
|
|
chord->notes().clear();
|
|
|
|
chord->setTuplet(tupletDst[voice]);
|
|
|
|
chord->setTrack(dtrack + voice);
|
|
|
|
undoAddElement(chord);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
Note* nnote = new Note(*note);
|
|
|
|
if (note->tieFor()) {
|
|
|
|
// Save the note and the tie for processing later.
|
|
|
|
// Use the end note as index in the map so, when this is found
|
|
|
|
// we know the tie has to be recreated.
|
|
|
|
oldTies.insert(note->tieFor()->endNote(), OldTie { note->tieFor(), nnote });
|
2020-02-19 16:29:57 +01:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
nnote->setTrack(dtrack + voice);
|
|
|
|
chord->add(nnote);
|
|
|
|
nnote->updateLine();
|
|
|
|
removeNotes.append(note);
|
|
|
|
createRestDst = false;
|
|
|
|
lengthDst = chord->actualDurationType();
|
|
|
|
|
|
|
|
// Is the note the last note of a tie?
|
|
|
|
if (oldTies.contains(note)) {
|
|
|
|
// Yes! Create a tie between the new notes and remove the
|
|
|
|
// old tie.
|
|
|
|
Tie* tie = oldTies[note].tie->clone();
|
|
|
|
tie->setStartNote(oldTies[note].nnote);
|
|
|
|
tie->setEndNote(nnote);
|
|
|
|
tie->setTrack(nnote->track());
|
|
|
|
undoAddElement(tie);
|
|
|
|
undoRemoveElement(oldTies[note].tie);
|
|
|
|
oldTies.remove(note);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
createRestSrc = false;
|
|
|
|
for (Note* note : removeNotes) {
|
|
|
|
undoRemoveElement(note);
|
|
|
|
Chord* chord = note->chord();
|
|
|
|
if (chord->notes().empty()) {
|
|
|
|
for (auto sp : spanner()) {
|
|
|
|
Slur* slur = toSlur(sp.second);
|
|
|
|
if (slur->type() != ElementType::SLUR) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (slur->startCR() == chord) {
|
|
|
|
slur->undoChangeProperty(Pid::TRACK, slur->track() + VOICES);
|
|
|
|
for (ScoreElement* ee : slur->linkList()) {
|
|
|
|
Slur* lslur = toSlur(ee);
|
|
|
|
lslur->setStartElement(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (slur->endCR() == chord) {
|
|
|
|
slur->undoChangeProperty(Pid::SPANNER_TRACK2, slur->track2() + VOICES);
|
|
|
|
for (ScoreElement* ee : slur->linkList()) {
|
|
|
|
Slur* lslur = toSlur(ee);
|
|
|
|
lslur->setEndElement(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
createRestSrc = true;
|
|
|
|
lengthSrc = chord->actualDurationType();
|
|
|
|
undoRemoveElement(chord);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-19 16:29:57 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
if (createRestSrc) {
|
|
|
|
addRest(s, strack + voice, lengthSrc, tupletSrc[voice]);
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
if (createRestDst) {
|
|
|
|
addRest(s, dtrack + voice, lengthDst, tupletDst[voice]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// cmdRemovePart
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::cmdRemovePart(Part* part)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int sidx = staffIdx(part);
|
|
|
|
int n = part->nstaves();
|
2014-08-16 14:18:06 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
cmdRemoveStaff(sidx);
|
|
|
|
}
|
2013-09-20 12:59:31 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
undoRemovePart(part, sidx);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// insertPart
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::insertPart(Part* part, int idx)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
bool inserted = false;
|
|
|
|
int staff = 0;
|
|
|
|
for (QList<Part*>::iterator i = _parts.begin(); i != _parts.end(); ++i) {
|
|
|
|
if (staff >= idx) {
|
|
|
|
_parts.insert(i, part);
|
|
|
|
inserted = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
staff += (*i)->nstaves();
|
|
|
|
}
|
|
|
|
if (!inserted) {
|
|
|
|
_parts.push_back(part);
|
|
|
|
}
|
|
|
|
masterScore()->rebuildMidiMapping();
|
|
|
|
setInstrumentsChanged(true);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// removePart
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::removePart(Part* part)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_parts.removeAt(_parts.indexOf(part));
|
|
|
|
masterScore()->rebuildMidiMapping();
|
|
|
|
setInstrumentsChanged(true);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// insertStaff
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-08-11 15:25:55 +02:00
|
|
|
void Score::insertStaff(Staff* staff, int ridx)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
staff->part()->insertStaff(staff, ridx);
|
|
|
|
|
|
|
|
int idx = staffIdx(staff->part()) + ridx;
|
|
|
|
_staves.insert(idx, staff);
|
|
|
|
|
|
|
|
for (auto i = staff->score()->spanner().cbegin(); i != staff->score()->spanner().cend(); ++i) {
|
|
|
|
Spanner* s = i->second;
|
|
|
|
if (s->systemFlag()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (s->staffIdx() >= idx) {
|
|
|
|
int t = s->track() + VOICES;
|
|
|
|
if (t >= ntracks()) {
|
|
|
|
t = ntracks() - 1;
|
|
|
|
}
|
|
|
|
s->setTrack(t);
|
|
|
|
for (SpannerSegment* ss : s->spannerSegments()) {
|
|
|
|
ss->setTrack(t);
|
|
|
|
}
|
|
|
|
if (s->track2() != -1) {
|
|
|
|
t = s->track2() + VOICES;
|
|
|
|
s->setTrack2(t < ntracks() ? t : s->track());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-02 10:27:32 +02:00
|
|
|
#if 0
|
2020-05-26 15:54:26 +02:00
|
|
|
for (Spanner* s : staff->score()->unmanagedSpanners()) {
|
|
|
|
if (s->systemFlag()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (s->staffIdx() >= idx) {
|
|
|
|
int t = s->track() + VOICES;
|
|
|
|
s->setTrack(t < ntracks() ? t : ntracks() - 1);
|
|
|
|
if (s->track2() != -1) {
|
|
|
|
t = s->track2() + VOICES;
|
|
|
|
s->setTrack2(t < ntracks() ? t : s->track());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-02 10:27:32 +02:00
|
|
|
#endif
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2014-08-16 14:18:06 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// removeStaff
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::removeStaff(Staff* staff)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int idx = staff->idx();
|
|
|
|
for (auto i = staff->score()->spanner().cbegin(); i != staff->score()->spanner().cend(); ++i) {
|
|
|
|
Spanner* s = i->second;
|
|
|
|
if (s->staffIdx() > idx) {
|
|
|
|
int t = s->track() - VOICES;
|
|
|
|
if (t < 0) {
|
|
|
|
t = 0;
|
|
|
|
}
|
|
|
|
s->setTrack(t);
|
|
|
|
for (SpannerSegment* ss : s->spannerSegments()) {
|
|
|
|
ss->setTrack(t);
|
|
|
|
}
|
|
|
|
if (s->track2() != -1) {
|
|
|
|
t = s->track2() - VOICES;
|
|
|
|
s->setTrack2(t >= 0 ? t : s->track());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-02 10:27:32 +02:00
|
|
|
#if 0
|
2020-05-26 15:54:26 +02:00
|
|
|
for (Spanner* s : staff->score()->unmanagedSpanners()) {
|
|
|
|
if (s->staffIdx() > idx) {
|
|
|
|
int t = s->track() - VOICES;
|
|
|
|
s->setTrack(t >= 0 ? t : 0);
|
|
|
|
if (s->track2() != -1) {
|
|
|
|
t = s->track2() - VOICES;
|
|
|
|
s->setTrack2(t >= 0 ? t : s->track());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-02 10:27:32 +02:00
|
|
|
#endif
|
2020-05-26 15:54:26 +02:00
|
|
|
_staves.removeAll(staff);
|
|
|
|
staff->part()->removeStaff(staff);
|
|
|
|
}
|
2014-08-16 14:18:06 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// adjustBracketsDel
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::adjustBracketsDel(int sidx, int eidx)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
|
|
|
|
Staff* staff = _staves[staffIdx];
|
|
|
|
for (BracketItem* bi : staff->brackets()) {
|
|
|
|
int span = bi->bracketSpan();
|
|
|
|
if ((span == 0) || ((staffIdx + span) < sidx) || (staffIdx > eidx)) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-10-14 00:35:11 +02:00
|
|
|
if ((sidx >= staffIdx) && (eidx <= (staffIdx + span))) {
|
2020-05-26 15:54:26 +02:00
|
|
|
bi->undoChangeProperty(Pid::BRACKET_SPAN, span - (eidx - sidx));
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
#if 0 // TODO
|
|
|
|
int span = staff->barLineSpan();
|
|
|
|
if ((sidx >= staffIdx) && (eidx <= (staffIdx + span))) {
|
|
|
|
int newSpan = span - (eidx - sidx) + 1;
|
|
|
|
int lastSpannedStaffIdx = staffIdx + newSpan - 1;
|
|
|
|
int tick = 0;
|
|
|
|
undoChangeBarLineSpan(staff, newSpan, 0, (_staves[lastSpannedStaffIdx]->lines(0) - 1) * 2);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// adjustBracketsIns
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::adjustBracketsIns(int sidx, int eidx)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
|
|
|
|
Staff* staff = _staves[staffIdx];
|
|
|
|
for (BracketItem* bi : staff->brackets()) {
|
|
|
|
int span = bi->bracketSpan();
|
|
|
|
if ((span == 0) || ((staffIdx + span) < sidx) || (staffIdx > eidx)) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-08-16 17:54:10 +02:00
|
|
|
if ((sidx >= staffIdx) && (eidx < (staffIdx + span))) {
|
2020-05-26 15:54:26 +02:00
|
|
|
bi->undoChangeProperty(Pid::BRACKET_SPAN, span + (eidx - sidx));
|
2012-05-26 14:26:10 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
#if 0 // TODO
|
|
|
|
int span = staff->barLineSpan();
|
|
|
|
if ((sidx >= staffIdx) && (eidx < (staffIdx + span))) {
|
|
|
|
int idx = staffIdx + span - 1;
|
|
|
|
if (idx >= _staves.size()) {
|
|
|
|
idx = _staves.size() - 1;
|
|
|
|
}
|
|
|
|
undoChangeBarLineSpan(staff, span, 0, (_staves[idx]->lines() - 1) * 2);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2013-09-11 16:13:43 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// adjustKeySigs
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::adjustKeySigs(int sidx, int eidx, KeyList km)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (int staffIdx = sidx; staffIdx < eidx; ++staffIdx) {
|
|
|
|
Staff* staff = _staves[staffIdx];
|
|
|
|
for (auto i = km.begin(); i != km.end(); ++i) {
|
|
|
|
Fraction tick = Fraction::fromTicks(i->first);
|
|
|
|
Measure* measure = tick2measure(tick);
|
|
|
|
if (!measure) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (staff->isDrumStaff(tick)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
KeySigEvent oKey = i->second;
|
|
|
|
KeySigEvent nKey = oKey;
|
|
|
|
int diff = -staff->part()->instrument(tick)->transpose().chromatic;
|
|
|
|
if (diff != 0 && !styleB(Sid::concertPitch) && !oKey.custom() && !oKey.isAtonal()) {
|
|
|
|
nKey.setKey(transposeKey(nKey.key(), diff, staff->part()->preferSharpFlat()));
|
|
|
|
}
|
|
|
|
staff->setKey(tick, nKey);
|
|
|
|
KeySig* keysig = new KeySig(this);
|
|
|
|
keysig->setTrack(staffIdx * VOICES);
|
|
|
|
keysig->setKeySigEvent(nKey);
|
|
|
|
Segment* s = measure->getSegment(SegmentType::KeySig, tick);
|
|
|
|
s->add(keysig);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-09-11 16:13:43 +02:00
|
|
|
|
2012-05-26 14:26:10 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// cmdRemoveStaff
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::cmdRemoveStaff(int staffIdx)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Staff* s = staff(staffIdx);
|
|
|
|
adjustBracketsDel(staffIdx, staffIdx + 1);
|
|
|
|
|
|
|
|
undoRemoveStaff(s);
|
|
|
|
|
|
|
|
// remove linked staff and measures in linked staves in excerpts
|
|
|
|
// unlink staff in the same score
|
|
|
|
|
|
|
|
if (s->links()) {
|
|
|
|
Staff* sameScoreLinkedStaff = 0;
|
|
|
|
auto staves = s->links();
|
|
|
|
for (auto le : *staves) {
|
|
|
|
Staff* staff = toStaff(le);
|
|
|
|
if (staff == s) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Score* lscore = staff->score();
|
|
|
|
if (lscore != this) {
|
|
|
|
lscore->undoRemoveStaff(staff);
|
|
|
|
s->score()->undo(new Unlink(staff));
|
|
|
|
if (staff->part()->nstaves() == 0) {
|
|
|
|
int pIndex = lscore->staffIdx(staff->part());
|
|
|
|
lscore->undoRemovePart(staff->part(), pIndex);
|
|
|
|
}
|
|
|
|
} else { // linked staff in the same score
|
|
|
|
sameScoreLinkedStaff = staff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sameScoreLinkedStaff) {
|
2018-04-27 13:29:20 +02:00
|
|
|
// s->score()->undo(new Unlink(sameScoreLinkedStaff)); // once should be enough
|
2020-05-26 15:54:26 +02:00
|
|
|
s->score()->undo(new Unlink(s)); // once should be enough
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// sortStaves
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::sortStaves(QList<int>& dst)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
qDeleteAll(systems());
|
|
|
|
systems().clear(); //??
|
|
|
|
_parts.clear();
|
|
|
|
Part* curPart = 0;
|
|
|
|
QList<Staff*> dl;
|
|
|
|
foreach (int idx, dst) {
|
|
|
|
Staff* staff = _staves[idx];
|
|
|
|
if (staff->part() != curPart) {
|
|
|
|
curPart = staff->part();
|
|
|
|
curPart->staves()->clear();
|
|
|
|
_parts.push_back(curPart);
|
|
|
|
}
|
|
|
|
curPart->staves()->push_back(staff);
|
|
|
|
dl.push_back(staff);
|
|
|
|
}
|
|
|
|
_staves = dl;
|
|
|
|
|
|
|
|
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
|
|
|
|
m->sortStaves(dst);
|
|
|
|
if (m->hasMMRest()) {
|
|
|
|
m->mmRest()->sortStaves(dst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (auto i : _spanner.map()) {
|
|
|
|
Spanner* sp = i.second;
|
|
|
|
if (sp->systemFlag()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
int voice = sp->voice();
|
|
|
|
int staffIdx = sp->staffIdx();
|
|
|
|
int idx = dst.indexOf(staffIdx);
|
|
|
|
if (idx >= 0) {
|
|
|
|
sp->setTrack(idx * VOICES + voice);
|
|
|
|
if (sp->track2() != -1) {
|
|
|
|
sp->setTrack2(idx * VOICES + (sp->track2() % VOICES)); // at least keep the voice...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setLayoutAll();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// cmdConcertPitchChanged
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-04-09 14:13:22 +02:00
|
|
|
void Score::cmdConcertPitchChanged(bool flag, bool /*useDoubleSharpsFlats*/)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
undoChangeStyleVal(Sid::concertPitch, flag); // change style flag
|
|
|
|
|
|
|
|
for (Staff* staff : _staves) {
|
|
|
|
if (staff->staffType(Fraction(0,1))->group() == StaffGroup::PERCUSSION) { // TODO
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// if this staff has no transposition, and no instrument changes, we can skip it
|
|
|
|
Interval interval = staff->part()->instrument()->transpose();
|
|
|
|
if (interval.isZero() && staff->part()->instruments()->size() == 1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!flag) {
|
|
|
|
interval.flip();
|
|
|
|
}
|
|
|
|
|
|
|
|
int staffIdx = staff->idx();
|
|
|
|
int startTrack = staffIdx * VOICES;
|
|
|
|
int endTrack = startTrack + VOICES;
|
|
|
|
|
|
|
|
transposeKeys(staffIdx, staffIdx + 1, Fraction(0,1), lastSegment()->tick(), interval, true, !flag);
|
|
|
|
|
|
|
|
for (Segment* segment = firstSegment(SegmentType::ChordRest); segment;
|
|
|
|
segment = segment->next1(SegmentType::ChordRest)) {
|
|
|
|
interval = staff->part()->instrument(segment->tick())->transpose();
|
|
|
|
if (!flag) {
|
|
|
|
interval.flip();
|
|
|
|
}
|
|
|
|
for (Element* e : segment->annotations()) {
|
|
|
|
if (!e->isHarmony() || (e->track() < startTrack) || (e->track() >= endTrack)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Harmony* h = toHarmony(e);
|
|
|
|
int rootTpc = transposeTpc(h->rootTpc(), interval, true);
|
|
|
|
int baseTpc = transposeTpc(h->baseTpc(), interval, true);
|
|
|
|
for (ScoreElement* se : h->linkList()) {
|
|
|
|
// don't transpose all links
|
|
|
|
// just ones resulting from mmrests
|
|
|
|
Harmony* he = toHarmony(se); // toHarmony() does not work as e is an ScoreElement
|
|
|
|
if (he->staff() == h->staff()) {
|
|
|
|
undoTransposeHarmony(he, rootTpc, baseTpc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//realized harmony should be invalid after a transpose command
|
|
|
|
Q_ASSERT(!h->realizedHarmony().valid());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// addAudioTrack
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::addAudioTrack()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// padToggle
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-10-11 03:47:55 +02:00
|
|
|
void Score::padToggle(Pad n, const EditData& ed)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int oldDots = _is.duration().dots();
|
|
|
|
switch (n) {
|
|
|
|
case Pad::NOTE00:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_LONG);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE0:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_BREVE);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE1:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_WHOLE);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE2:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_HALF);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE4:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_QUARTER);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE8:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_EIGHTH);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE16:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_16TH);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE32:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_32ND);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE64:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_64TH);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE128:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_128TH);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE256:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_256TH);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE512:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_512TH);
|
|
|
|
break;
|
|
|
|
case Pad::NOTE1024:
|
|
|
|
_is.setDuration(TDuration::DurationType::V_1024TH);
|
|
|
|
break;
|
|
|
|
case Pad::REST:
|
|
|
|
if (noteEntryMode()) {
|
|
|
|
_is.setRest(!_is.rest());
|
|
|
|
_is.setAccidentalType(AccidentalType::NONE);
|
|
|
|
} else if (selection().isNone()) {
|
|
|
|
ed.view->startNoteEntryMode();
|
|
|
|
_is.setDuration(TDuration::DurationType::V_QUARTER);
|
|
|
|
_is.setRest(true);
|
|
|
|
} else {
|
|
|
|
for (ChordRest* cr : getSelectedChordRests()) {
|
|
|
|
if (!cr->isRest()) {
|
|
|
|
setNoteRest(cr->segment(), cr->track(), NoteVal(), cr->durationTypeTicks());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Pad::DOT:
|
|
|
|
if ((_is.duration().dots() == 1) || (_is.duration() == TDuration::DurationType::V_1024TH)) {
|
|
|
|
_is.setDots(0);
|
|
|
|
} else {
|
|
|
|
_is.setDots(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Pad::DOTDOT:
|
|
|
|
if ((_is.duration().dots() == 2)
|
|
|
|
|| (_is.duration() == TDuration::DurationType::V_512TH)
|
|
|
|
|| (_is.duration() == TDuration::DurationType::V_1024TH)) {
|
|
|
|
_is.setDots(0);
|
|
|
|
} else {
|
|
|
|
_is.setDots(2);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Pad::DOT3:
|
|
|
|
if ((_is.duration().dots() == 3)
|
|
|
|
|| (_is.duration() == TDuration::DurationType::V_256TH)
|
|
|
|
|| (_is.duration() == TDuration::DurationType::V_512TH)
|
|
|
|
|| (_is.duration() == TDuration::DurationType::V_1024TH)) {
|
2012-05-26 14:26:10 +02:00
|
|
|
_is.setDots(0);
|
2020-05-26 15:54:26 +02:00
|
|
|
} else {
|
|
|
|
_is.setDots(3);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Pad::DOT4:
|
|
|
|
if ((_is.duration().dots() == 4)
|
|
|
|
|| (_is.duration() == TDuration::DurationType::V_128TH)
|
|
|
|
|| (_is.duration() == TDuration::DurationType::V_256TH)
|
|
|
|
|| (_is.duration() == TDuration::DurationType::V_512TH)
|
|
|
|
|| (_is.duration() == TDuration::DurationType::V_1024TH)) {
|
|
|
|
_is.setDots(0);
|
|
|
|
} else {
|
|
|
|
_is.setDots(4);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (n >= Pad::NOTE00 && n <= Pad::NOTE1024) {
|
|
|
|
_is.setDots(0);
|
|
|
|
//
|
|
|
|
// if in "note enter" mode, reset
|
|
|
|
// rest flag
|
|
|
|
//
|
|
|
|
if (noteEntryMode()) {
|
|
|
|
if (usingNoteEntryMethod(NoteEntryMethod::RHYTHM)) {
|
|
|
|
switch (oldDots) {
|
|
|
|
case 1:
|
|
|
|
padToggle(Pad::DOT, ed);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
padToggle(Pad::DOTDOT, ed);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
NoteVal nval;
|
|
|
|
if (_is.rest()) {
|
|
|
|
// Enter a rest
|
|
|
|
nval = NoteVal();
|
|
|
|
} else {
|
|
|
|
// Enter a note on the middle staff line
|
|
|
|
Staff* s = staff(_is.track() / VOICES);
|
|
|
|
Fraction tick = _is.tick();
|
|
|
|
ClefType clef = s->clef(tick);
|
|
|
|
Key key = s->key(tick);
|
|
|
|
nval = NoteVal(line2pitch(4, clef, key));
|
|
|
|
}
|
|
|
|
setNoteRest(_is.segment(), _is.track(), nval, _is.duration().fraction());
|
|
|
|
_is.moveToNextInputPos();
|
|
|
|
} else {
|
|
|
|
_is.setRest(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (noteEntryMode()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<ChordRest*> crs;
|
|
|
|
|
|
|
|
if (selection().isSingle()) {
|
|
|
|
Element* e = selection().element();
|
|
|
|
ChordRest* cr = InputState::chordRest(e);
|
|
|
|
|
|
|
|
// do not allow to add a dot on a full measure rest
|
|
|
|
if (cr && cr->isRest()) {
|
|
|
|
Rest* r = toRest(cr);
|
|
|
|
if (r->isFullMeasureRest()) {
|
|
|
|
_is.setDots(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// on measure rest, select the first actual rest
|
|
|
|
if (cr && cr->isRest() && cr->measure()->isMMRest()) {
|
|
|
|
Measure* m = cr->measure()->mmRestFirst();
|
|
|
|
if (m) {
|
|
|
|
cr = m->findChordRest(m->tick(), 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cr) {
|
|
|
|
crs.push_back(cr);
|
|
|
|
} else {
|
|
|
|
ed.view->startNoteEntryMode();
|
|
|
|
deselect(e);
|
|
|
|
}
|
|
|
|
} else if (selection().isNone() && n != Pad::REST) {
|
|
|
|
TDuration td = _is.duration();
|
|
|
|
ed.view->startNoteEntryMode();
|
|
|
|
_is.setDuration(td);
|
|
|
|
_is.setAccidentalType(AccidentalType::NONE);
|
|
|
|
} else {
|
|
|
|
const auto elements = selection().uniqueElements();
|
|
|
|
bool canAdjustLength = true;
|
|
|
|
for (Element* e : elements) {
|
|
|
|
ChordRest* cr = InputState::chordRest(e);
|
|
|
|
if (!cr) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (cr->isRepeatMeasure() || (cr->isRest() && toRest(cr)->measure() && toRest(cr)->measure()->isMMRest())) {
|
|
|
|
canAdjustLength = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
crs.push_back(cr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (canAdjustLength) {
|
|
|
|
// Change length from last to first chord/rest
|
|
|
|
std::sort(crs.begin(), crs.end(), [](const ChordRest* cr1, const ChordRest* cr2) {
|
|
|
|
if (cr2->track() == cr1->track()) {
|
|
|
|
return cr2->isBefore(cr1);
|
|
|
|
}
|
|
|
|
return cr2->track() < cr1->track();
|
|
|
|
});
|
|
|
|
// Remove duplicates from the list
|
|
|
|
crs.erase(std::unique(crs.begin(), crs.end()), crs.end());
|
|
|
|
} else {
|
|
|
|
crs.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ChordRest* cr : crs) {
|
|
|
|
if (cr->isChord() && (toChord(cr)->isGrace())) {
|
2012-05-26 14:26:10 +02:00
|
|
|
//
|
2020-05-26 15:54:26 +02:00
|
|
|
// handle appoggiatura and acciaccatura
|
2012-05-26 14:26:10 +02:00
|
|
|
//
|
2020-05-26 15:54:26 +02:00
|
|
|
undoChangeChordRestLen(cr, _is.duration());
|
|
|
|
} else {
|
|
|
|
changeCRlen(cr, _is.duration());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// deselect
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::deselect(Element* el)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
addRefresh(el->abbox());
|
|
|
|
_selection.remove(el);
|
|
|
|
setSelectionChanged(true);
|
|
|
|
_selection.update();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// select
|
|
|
|
// staffIdx is valid, if element is of type MEASURE
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::select(Element* e, SelectType type, int staffIdx)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
// Move the playhead to the selected element's preferred play position.
|
|
|
|
if (e) {
|
|
|
|
const auto playTick = e->playTick();
|
|
|
|
if (masterScore()->playPos() != playTick) {
|
|
|
|
masterScore()->setPlayPos(playTick);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (MScore::debugMode) {
|
|
|
|
qDebug("select element <%s> type %d(state %d) staff %d",
|
2015-10-27 11:30:09 +01:00
|
|
|
e ? e->name() : "", int(type), int(selection().state()), e ? e->staffIdx() : -1);
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case SelectType::SINGLE:
|
|
|
|
selectSingle(e, staffIdx);
|
|
|
|
break;
|
|
|
|
case SelectType::ADD:
|
|
|
|
selectAdd(e);
|
|
|
|
break;
|
|
|
|
case SelectType::RANGE:
|
|
|
|
selectRange(e, staffIdx);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
setSelectionChanged(true);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2014-05-24 22:24:48 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// selectSingle
|
|
|
|
// staffIdx is valid, if element is of type MEASURE
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::selectSingle(Element* e, int staffIdx)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
SelState selState = _selection.state();
|
|
|
|
deselectAll();
|
|
|
|
if (e == 0) {
|
|
|
|
selState = SelState::NONE;
|
|
|
|
setUpdateAll();
|
|
|
|
} else {
|
|
|
|
if (e->isMeasure()) {
|
|
|
|
select(e, SelectType::RANGE, staffIdx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
addRefresh(e->abbox());
|
|
|
|
_selection.add(e);
|
|
|
|
_is.setTrack(e->track());
|
|
|
|
selState = SelState::LIST;
|
|
|
|
if (e->type() == ElementType::NOTE) {
|
|
|
|
e = e->parent();
|
|
|
|
}
|
|
|
|
if (e->isChordRest()) {
|
|
|
|
_is.setLastSegment(_is.segment());
|
|
|
|
_is.setSegment(toChordRest(e)->segment());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_selection.setActiveSegment(0);
|
|
|
|
_selection.setActiveTrack(0);
|
|
|
|
|
|
|
|
_selection.setState(selState);
|
|
|
|
}
|
2014-05-24 22:24:48 +02:00
|
|
|
|
2018-08-31 11:54:16 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// switchToPageMode
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::switchToPageMode()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (_layoutMode != LayoutMode::PAGE) {
|
|
|
|
setLayoutMode(LayoutMode::PAGE);
|
|
|
|
doLayout();
|
|
|
|
}
|
|
|
|
}
|
2018-08-31 11:54:16 +02:00
|
|
|
|
2014-05-24 22:24:48 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// selectAdd
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::selectAdd(Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
SelState selState = _selection.state();
|
|
|
|
|
|
|
|
if (_selection.isRange()) {
|
|
|
|
select(0, SelectType::SINGLE, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e->isMeasure()) {
|
|
|
|
Measure* m = toMeasure(e);
|
|
|
|
Fraction tick = m->tick();
|
|
|
|
if (_selection.isNone()) {
|
|
|
|
_selection.setRange(m->tick2segment(tick),
|
|
|
|
m == lastMeasure() ? 0 : m->last(),
|
|
|
|
0,
|
|
|
|
nstaves());
|
|
|
|
setUpdateAll();
|
|
|
|
selState = SelState::RANGE;
|
|
|
|
_selection.updateSelectedElements();
|
|
|
|
}
|
|
|
|
} else if (!_selection.elements().contains(e)) {
|
|
|
|
addRefresh(e->abbox());
|
|
|
|
selState = SelState::LIST;
|
|
|
|
_selection.add(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
_selection.setState(selState);
|
|
|
|
}
|
2014-05-24 12:53:50 +02:00
|
|
|
|
2014-05-24 22:24:48 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// selectRange
|
|
|
|
// staffIdx is valid, if element is of type MEASURE
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::selectRange(Element* e, int staffIdx)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int activeTrack = e->track();
|
|
|
|
// current selection is range extending to end of score?
|
|
|
|
bool endRangeSelected = selection().isRange() && selection().endSegment() == nullptr;
|
|
|
|
if (e->isMeasure()) {
|
|
|
|
Measure* m = toMeasure(e);
|
|
|
|
Fraction tick = m->tick();
|
|
|
|
Fraction etick = tick + m->ticks();
|
|
|
|
activeTrack = staffIdx * VOICES;
|
|
|
|
Segment* s1 = m->tick2segment(tick);
|
|
|
|
if (!s1) { // m is corrupted!
|
|
|
|
s1 = m->first(SegmentType::ChordRest);
|
|
|
|
}
|
|
|
|
Segment* s2 = m == lastMeasure() ? 0 : m->last();
|
|
|
|
if (_selection.isNone() || (_selection.isList() && !_selection.isSingle())) {
|
|
|
|
if (_selection.isList()) {
|
|
|
|
deselectAll();
|
|
|
|
}
|
|
|
|
_selection.setRange(s1, s2, staffIdx, staffIdx + 1);
|
|
|
|
} else if (_selection.isRange()) {
|
|
|
|
_selection.extendRangeSelection(s1, s2, staffIdx, tick, etick);
|
|
|
|
} else if (_selection.isSingle()) {
|
|
|
|
Element* oe = selection().element();
|
|
|
|
if (oe->isNote() || oe->isChordRest()) {
|
|
|
|
if (oe->isNote()) {
|
|
|
|
oe = oe->parent();
|
|
|
|
}
|
|
|
|
ChordRest* cr = toChordRest(oe);
|
|
|
|
Fraction oetick = cr->segment()->tick();
|
|
|
|
Segment* startSegment = cr->segment();
|
|
|
|
Segment* endSegment = m->last();
|
|
|
|
if (tick < oetick) {
|
|
|
|
startSegment = m->tick2segment(tick);
|
|
|
|
if (etick <= oetick) {
|
|
|
|
SegmentType st = SegmentType::ChordRest | SegmentType::EndBarLine | SegmentType::Clef;
|
|
|
|
endSegment = cr->nextSegmentAfterCR(st);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int staffStart = staffIdx;
|
|
|
|
int endStaff = staffIdx + 1;
|
|
|
|
if (staffStart > cr->staffIdx()) {
|
|
|
|
staffStart = cr->staffIdx();
|
|
|
|
} else if (cr->staffIdx() >= endStaff) {
|
|
|
|
endStaff = cr->staffIdx() + 1;
|
|
|
|
}
|
|
|
|
_selection.setRange(startSegment, endSegment, staffStart, endStaff);
|
|
|
|
} else {
|
|
|
|
deselectAll();
|
|
|
|
_selection.setRange(s1, s2, staffIdx, staffIdx + 1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
qDebug("SELECT_RANGE: measure: sel state %d", int(_selection.state()));
|
2014-05-24 22:24:48 +02:00
|
|
|
return;
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
} else if (e->isNote() || e->isChordRest()) {
|
|
|
|
if (e->isNote()) {
|
|
|
|
e = e->parent();
|
|
|
|
}
|
|
|
|
ChordRest* cr = toChordRest(e);
|
|
|
|
|
|
|
|
if (_selection.isNone() || (_selection.isList() && !_selection.isSingle())) {
|
|
|
|
if (_selection.isList()) {
|
|
|
|
deselectAll();
|
|
|
|
}
|
|
|
|
SegmentType st = SegmentType::ChordRest | SegmentType::EndBarLine | SegmentType::Clef;
|
|
|
|
_selection.setRange(cr->segment(), cr->nextSegmentAfterCR(st), e->staffIdx(), e->staffIdx() + 1);
|
|
|
|
activeTrack = cr->track();
|
|
|
|
} else if (_selection.isSingle()) {
|
|
|
|
Element* oe = _selection.element();
|
|
|
|
if (oe && (oe->isNote() || oe->isRest())) {
|
|
|
|
if (oe->isNote()) {
|
|
|
|
oe = oe->parent();
|
|
|
|
}
|
|
|
|
ChordRest* ocr = toChordRest(oe);
|
|
|
|
|
|
|
|
Segment* endSeg = tick2segmentMM(ocr->segment()->tick() + ocr->actualTicks());
|
|
|
|
if (!endSeg) {
|
|
|
|
endSeg = ocr->segment()->next();
|
|
|
|
}
|
|
|
|
|
|
|
|
_selection.setRange(ocr->segment(), endSeg, oe->staffIdx(), oe->staffIdx() + 1);
|
|
|
|
_selection.extendRangeSelection(cr);
|
|
|
|
} else {
|
|
|
|
select(e, SelectType::SINGLE, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (_selection.isRange()) {
|
|
|
|
_selection.extendRangeSelection(cr);
|
|
|
|
} else {
|
|
|
|
qDebug("sel state %d", int(_selection.state()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!endRangeSelected && !_selection.endSegment()) {
|
|
|
|
_selection.setEndSegment(cr->segment()->nextCR());
|
|
|
|
}
|
|
|
|
if (!_selection.startSegment()) {
|
|
|
|
_selection.setStartSegment(cr->segment());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
select(e, SelectType::SINGLE, staffIdx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_selection.setActiveTrack(activeTrack);
|
|
|
|
|
|
|
|
// doing this in note entry mode can clear selection
|
|
|
|
if (_selection.startSegment() && !noteEntryMode()) {
|
|
|
|
Fraction tick = _selection.startSegment()->tick();
|
|
|
|
if (masterScore()->playPos() != tick) {
|
|
|
|
masterScore()->setPlayPos(tick);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_selection.updateSelectedElements();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2014-07-15 00:28:31 +02:00
|
|
|
// collectMatch
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::collectMatch(void* data, Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
ElementPattern* p = static_cast<ElementPattern*>(data);
|
|
|
|
if (p->type != int(e->type())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p->type == int(ElementType::NOTE)) {
|
|
|
|
if (p->subtype < 0) {
|
|
|
|
if (!(toNote(e)->chord()->isGrace())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if ((toNote(e)->chord()->isGrace()) || (p->subtype != e->subtype())) {
|
2014-07-15 00:28:31 +02:00
|
|
|
return;
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
} else if (p->subtypeValid && p->subtype != e->subtype()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((p->staffStart != -1)
|
|
|
|
&& ((p->staffStart > e->staffIdx()) || (p->staffEnd <= e->staffIdx()))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p->voice != -1 && p->voice != e->voice()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p->system) {
|
|
|
|
Element* ee = e;
|
|
|
|
do {
|
|
|
|
if (ee->type() == ElementType::SYSTEM) {
|
|
|
|
if (p->system != ee) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ee = ee->parent();
|
|
|
|
} while (ee);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e->isRest() && p->durationTicks != Fraction(-1,1)) {
|
|
|
|
const Rest* r = toRest(e);
|
|
|
|
if (p->durationTicks != r->actualTicks()) {
|
2017-08-01 11:32:33 +02:00
|
|
|
return;
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
}
|
2017-08-01 11:32:33 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
p->el.append(e);
|
|
|
|
}
|
2014-07-15 00:28:31 +02:00
|
|
|
|
2016-09-22 16:07:47 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// collectNoteMatch
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::collectNoteMatch(void* data, Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
NotePattern* p = static_cast<NotePattern*>(data);
|
|
|
|
if (!e->isNote()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Note* n = toNote(e);
|
|
|
|
if (p->type != NoteType::INVALID && p->type != n->noteType()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p->pitch != -1 && p->pitch != n->pitch()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p->string != STRING_NONE && p->string != n->string()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p->tpc != Tpc::TPC_INVALID && p->tpc != n->tpc()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p->notehead != NoteHead::Group::HEAD_INVALID && p->notehead != n->headGroup()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p->durationType.type() != TDuration::DurationType::V_INVALID
|
|
|
|
&& p->durationType != n->chord()->actualDurationType()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p->durationTicks != Fraction(-1,1) && p->durationTicks != n->chord()->actualTicks()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ((p->staffStart != -1)
|
|
|
|
&& ((p->staffStart > e->staffIdx()) || (p->staffEnd <= e->staffIdx()))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p->voice != -1 && p->voice != e->voice()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p->system && (p->system != n->chord()->segment()->system())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
p->el.append(n);
|
|
|
|
}
|
2016-09-22 16:07:47 +02:00
|
|
|
|
2014-08-15 17:20:20 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// selectSimilar
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-07-15 00:28:31 +02:00
|
|
|
void Score::selectSimilar(Element* e, bool sameStaff)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
ElementType type = e->type();
|
|
|
|
Score* score = e->score();
|
|
|
|
|
|
|
|
ElementPattern pattern;
|
|
|
|
pattern.type = int(type);
|
|
|
|
pattern.subtype = 0;
|
|
|
|
pattern.subtypeValid = false;
|
|
|
|
if (type == ElementType::NOTE) {
|
|
|
|
if (toNote(e)->chord()->isGrace()) {
|
|
|
|
pattern.subtype = -1; // hack
|
|
|
|
} else {
|
|
|
|
pattern.subtype = e->subtype();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pattern.staffStart = sameStaff ? e->staffIdx() : -1;
|
|
|
|
pattern.staffEnd = sameStaff ? e->staffIdx() + 1 : -1;
|
|
|
|
pattern.voice = -1;
|
|
|
|
pattern.system = 0;
|
|
|
|
pattern.durationTicks = Fraction(-1,1);
|
|
|
|
|
|
|
|
score->scanElements(&pattern, collectMatch);
|
|
|
|
|
|
|
|
score->select(0, SelectType::SINGLE, 0);
|
|
|
|
for (Element* ee : pattern.el) {
|
|
|
|
score->select(ee, SelectType::ADD, 0);
|
|
|
|
}
|
|
|
|
}
|
2014-07-15 00:28:31 +02:00
|
|
|
|
2014-08-15 17:20:20 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// selectSimilarInRange
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-07-15 00:28:31 +02:00
|
|
|
void Score::selectSimilarInRange(Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
ElementType type = e->type();
|
|
|
|
Score* score = e->score();
|
|
|
|
|
|
|
|
ElementPattern pattern;
|
|
|
|
pattern.type = int(type);
|
|
|
|
pattern.subtype = 0;
|
|
|
|
pattern.subtypeValid = false;
|
|
|
|
if (type == ElementType::NOTE) {
|
|
|
|
if (toNote(e)->chord()->isGrace()) {
|
|
|
|
pattern.subtype = -1; //hack
|
|
|
|
} else {
|
|
|
|
pattern.subtype = e->subtype();
|
|
|
|
}
|
|
|
|
pattern.subtypeValid = true;
|
|
|
|
}
|
|
|
|
pattern.staffStart = selection().staffStart();
|
|
|
|
pattern.staffEnd = selection().staffEnd();
|
|
|
|
pattern.voice = -1;
|
|
|
|
pattern.system = 0;
|
|
|
|
pattern.durationTicks = Fraction(-1,1);
|
|
|
|
|
|
|
|
score->scanElementsInRange(&pattern, collectMatch);
|
|
|
|
|
|
|
|
score->select(0, SelectType::SINGLE, 0);
|
|
|
|
for (Element* ee : pattern.el) {
|
|
|
|
score->select(ee, SelectType::ADD, 0);
|
|
|
|
}
|
|
|
|
}
|
2014-08-15 17:20:20 +02:00
|
|
|
|
2014-07-15 00:28:31 +02:00
|
|
|
//---------------------------------------------------------
|
2012-05-26 14:26:10 +02:00
|
|
|
// lassoSelect
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::lassoSelect(const QRectF& bbox)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
select(0, SelectType::SINGLE, 0);
|
|
|
|
QRectF fr(bbox.normalized());
|
|
|
|
foreach (Page* page, pages()) {
|
|
|
|
QRectF pr(page->bbox());
|
|
|
|
QRectF frr(fr.translated(-page->pos()));
|
|
|
|
if (pr.right() < frr.left()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (pr.left() > frr.right()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<Element*> el = page->items(frr);
|
|
|
|
for (int i = 0; i < el.size(); ++i) {
|
|
|
|
Element* e = el.at(i);
|
|
|
|
if (frr.contains(e->abbox())) {
|
|
|
|
if (e->type() != ElementType::MEASURE && e->selectable()) {
|
|
|
|
select(e, SelectType::ADD, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// lassoSelectEnd
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::lassoSelectEnd()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int noteRestCount = 0;
|
|
|
|
Segment* startSegment = 0;
|
|
|
|
Segment* endSegment = 0;
|
|
|
|
int startStaff = 0x7fffffff;
|
|
|
|
int endStaff = 0;
|
|
|
|
const ChordRest* endCR = 0;
|
|
|
|
|
|
|
|
if (_selection.elements().empty()) {
|
|
|
|
_selection.setState(SelState::NONE);
|
|
|
|
setUpdateAll();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_selection.setState(SelState::LIST);
|
|
|
|
|
|
|
|
foreach (const Element* e, _selection.elements()) {
|
|
|
|
if (e->type() != ElementType::NOTE && e->type() != ElementType::REST) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
++noteRestCount;
|
|
|
|
if (e->type() == ElementType::NOTE) {
|
|
|
|
e = e->parent();
|
|
|
|
}
|
|
|
|
Segment* seg = static_cast<const ChordRest*>(e)->segment();
|
|
|
|
if ((startSegment == 0) || (*seg < *startSegment)) {
|
|
|
|
startSegment = seg;
|
|
|
|
}
|
|
|
|
if ((endSegment == 0) || (*seg > *endSegment)) {
|
|
|
|
endSegment = seg;
|
|
|
|
endCR = static_cast<const ChordRest*>(e);
|
|
|
|
}
|
|
|
|
int idx = e->staffIdx();
|
|
|
|
if (idx < startStaff) {
|
|
|
|
startStaff = idx;
|
|
|
|
}
|
|
|
|
if (idx > endStaff) {
|
|
|
|
endStaff = idx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (noteRestCount > 0) {
|
|
|
|
endSegment = endCR->nextSegmentAfterCR(SegmentType::ChordRest
|
|
|
|
| SegmentType::EndBarLine
|
|
|
|
| SegmentType::Clef);
|
|
|
|
_selection.setRange(startSegment, endSegment, startStaff, endStaff + 1);
|
|
|
|
if (!_selection.isRange()) {
|
|
|
|
_selection.setState(SelState::RANGE);
|
|
|
|
}
|
|
|
|
_selection.updateSelectedElements();
|
|
|
|
}
|
|
|
|
setUpdateAll();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// addLyrics
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
void Score::addLyrics(const Fraction& tick, int staffIdx, const QString& txt)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (txt.trimmed().isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Measure* measure = tick2measure(tick);
|
|
|
|
Segment* seg = measure->findSegment(SegmentType::ChordRest, tick);
|
|
|
|
if (seg == 0) {
|
|
|
|
qDebug("no segment found for lyrics<%s> at tick %d",
|
2019-01-30 15:13:54 +01:00
|
|
|
qPrintable(txt), tick.ticks());
|
2020-05-26 15:54:26 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool lyricsAdded = false;
|
|
|
|
for (int voice = 0; voice < VOICES; ++voice) {
|
|
|
|
int track = staffIdx * VOICES + voice;
|
|
|
|
ChordRest* cr = toChordRest(seg->element(track));
|
|
|
|
if (cr) {
|
|
|
|
Lyrics* l = new Lyrics(this);
|
|
|
|
l->setXmlText(txt);
|
|
|
|
l->setTrack(track);
|
|
|
|
cr->add(l);
|
|
|
|
lyricsAdded = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!lyricsAdded) {
|
|
|
|
qDebug("no chord/rest for lyrics<%s> at tick %d, staff %d",
|
2019-01-30 15:13:54 +01:00
|
|
|
qPrintable(txt), tick.ticks(), staffIdx);
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setTempo
|
|
|
|
// convenience function to access TempoMap
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setTempo(Segment* segment, qreal tempo)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
setTempo(segment->tick(), tempo);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
void Score::setTempo(const Fraction& tick, qreal tempo)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
tempomap()->setTempo(tick.ticks(), tempo);
|
|
|
|
setPlaylistDirty();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// removeTempo
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
void Score::removeTempo(const Fraction& tick)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
tempomap()->delTempo(tick.ticks());
|
|
|
|
setPlaylistDirty();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2018-09-05 15:17:49 +02:00
|
|
|
// resetTempo
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-09-17 14:05:56 +02:00
|
|
|
void Score::resetTempo()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
resetTempoRange(Fraction(0,1), Fraction(std::numeric_limits<int>::max(), 1));
|
|
|
|
}
|
2018-10-30 11:15:18 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// resetTempoRange
|
|
|
|
// Reset tempo and timesig maps in the given range.
|
|
|
|
// Start tick included, end tick excluded.
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
void Score::resetTempoRange(const Fraction& tick1, const Fraction& tick2)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
const bool zeroInRange = (tick1 <= Fraction(0,1) && tick2 > Fraction(0,1));
|
|
|
|
tempomap()->clearRange(tick1.ticks(), tick2.ticks());
|
|
|
|
if (zeroInRange) {
|
|
|
|
tempomap()->setTempo(0, _defaultTempo);
|
|
|
|
}
|
|
|
|
sigmap()->clearRange(tick1.ticks(), tick2.ticks());
|
|
|
|
if (zeroInRange) {
|
|
|
|
Measure* m = firstMeasure();
|
|
|
|
if (m) {
|
|
|
|
sigmap()->add(0, SigEvent(m->ticks(), m->timesig(), 0));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-09-05 15:17:49 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2012-05-26 14:26:10 +02:00
|
|
|
// setPause
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
void Score::setPause(const Fraction& tick, qreal seconds)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
tempomap()->setPause(tick.ticks(), seconds);
|
|
|
|
setPlaylistDirty();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// tempo
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
qreal Score::tempo(const Fraction& tick) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return tempomap()->tempo(tick.ticks());
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// loWidth
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
qreal Score::loWidth() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return styleD(Sid::pageWidth) * DPI;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// loHeight
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
qreal Score::loHeight() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return styleD(Sid::pageHeight) * DPI;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// cmdSelectAll
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::cmdSelectAll()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (_measures.size() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
deselectAll();
|
|
|
|
Measure* first = firstMeasureMM();
|
|
|
|
if (!first) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Measure* last = lastMeasureMM();
|
|
|
|
selectRange(first, 0);
|
|
|
|
selectRange(last, nstaves() - 1);
|
|
|
|
setUpdateAll();
|
|
|
|
update();
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// cmdSelectSection
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::cmdSelectSection()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Segment* s = _selection.startSegment();
|
|
|
|
if (s == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
MeasureBase* sm = s->measure();
|
|
|
|
MeasureBase* em = sm;
|
|
|
|
while (sm->prev()) {
|
|
|
|
if (sm->prev()->sectionBreak()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
sm = sm->prev();
|
|
|
|
}
|
|
|
|
while (em->next()) {
|
|
|
|
if (em->sectionBreak()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
em = em->next();
|
|
|
|
}
|
|
|
|
while (sm && sm->type() != ElementType::MEASURE) {
|
|
|
|
sm = sm->next();
|
|
|
|
}
|
|
|
|
while (em && em->type() != ElementType::MEASURE) {
|
|
|
|
em = em->next();
|
|
|
|
}
|
|
|
|
if (sm == 0 || em == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_selection.setRange(toMeasure(sm)->first(), toMeasure(em)->last(), 0, nstaves());
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// undo
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2017-05-19 09:06:41 +02:00
|
|
|
void Score::undo(UndoCommand* cmd, EditData* ed) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
undoStack()->push(cmd, ed);
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// linkId
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Score::linkId()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return (masterScore()->_linkId)++;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2012-09-22 19:40:45 +02:00
|
|
|
// val is a used link id
|
2012-05-26 14:26:10 +02:00
|
|
|
void Score::linkId(int val)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Score* s = masterScore();
|
|
|
|
if (val >= s->_linkId) {
|
|
|
|
s->_linkId = val + 1; // update unused link id
|
|
|
|
}
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// scoreList
|
|
|
|
// return a list of scores containing the root score
|
|
|
|
// and all part scores (if there are any)
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QList<Score*> Score::scoreList()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QList<Score*> scores;
|
|
|
|
Score* root = masterScore();
|
|
|
|
scores.append(root);
|
|
|
|
for (const Excerpt* ex : root->excerpts()) {
|
|
|
|
if (ex->partScore()) {
|
|
|
|
scores.append(ex->partScore());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return scores;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// switchLayer
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::switchLayer(const QString& s)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int layerIdx = 0;
|
|
|
|
for (const Layer& l : layer()) {
|
|
|
|
if (s == l.name) {
|
|
|
|
setCurrentLayer(layerIdx);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
++layerIdx;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2012-05-26 14:26:10 +02:00
|
|
|
|
2012-06-07 09:24:05 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// appendPart
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::appendPart(const QString& name)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
static InstrumentTemplate defaultInstrument;
|
|
|
|
InstrumentTemplate* t;
|
|
|
|
|
|
|
|
t = searchTemplate(name);
|
|
|
|
if (t == 0) {
|
|
|
|
qDebug("appendPart: <%s> not found", qPrintable(name));
|
|
|
|
t = &defaultInstrument;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t->channel.empty()) {
|
|
|
|
Channel a;
|
|
|
|
a.setChorus(0);
|
|
|
|
a.setReverb(0);
|
|
|
|
a.setName(Channel::DEFAULT_NAME);
|
|
|
|
a.setBank(0);
|
|
|
|
a.setVolume(90);
|
|
|
|
a.setPan(0);
|
|
|
|
t->channel.append(a);
|
|
|
|
}
|
|
|
|
Part* part = new Part(this);
|
|
|
|
part->initFromInstrTemplate(t);
|
|
|
|
int n = nstaves();
|
|
|
|
for (int i = 0; i < t->nstaves(); ++i) {
|
|
|
|
Staff* staff = new Staff(this);
|
|
|
|
staff->setPart(part);
|
|
|
|
StaffType* stt = staff->staffType(Fraction(0,1));
|
|
|
|
stt->setLines(t->staffLines[i]);
|
|
|
|
stt->setSmall(t->smallStaff[i]);
|
|
|
|
if (i == 0) {
|
|
|
|
staff->setBracketType(0, t->bracket[0]);
|
|
|
|
staff->setBracketSpan(0, t->nstaves());
|
|
|
|
}
|
|
|
|
undoInsertStaff(staff, i);
|
|
|
|
}
|
|
|
|
part->staves()->front()->setBarLineSpan(part->nstaves());
|
|
|
|
undoInsertPart(part, n);
|
|
|
|
fixTicks();
|
|
|
|
masterScore()->rebuildMidiMapping();
|
|
|
|
}
|
2012-06-07 09:24:05 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// appendMeasures
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::appendMeasures(int n)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
insertMeasure(ElementType::MEASURE, 0, false);
|
|
|
|
}
|
|
|
|
}
|
2012-06-07 09:24:05 +02:00
|
|
|
|
2013-06-20 13:57:15 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// addSpanner
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::addSpanner(Spanner* s)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_spanner.addSpanner(s);
|
|
|
|
}
|
2013-06-20 13:57:15 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// removeSpanner
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::removeSpanner(Spanner* s)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_spanner.removeSpanner(s);
|
|
|
|
}
|
2013-06-20 13:57:15 +02:00
|
|
|
|
2013-06-10 11:03:34 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// isSpannerStartEnd
|
|
|
|
// does is spanner start or end at tick position tick
|
|
|
|
// for track ?
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
bool Score::isSpannerStartEnd(const Fraction& tick, int track) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (auto i : _spanner.map()) {
|
|
|
|
if (i.second->track() != track) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (i.second->tick() == tick || i.second->tick2() == tick) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2013-06-10 11:03:34 +02:00
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
void Score::insertTime(const Fraction& tick, const Fraction& len)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (Staff* staff : staves()) {
|
|
|
|
staff->insertTime(tick, len);
|
|
|
|
}
|
|
|
|
for (Part* part : parts()) {
|
|
|
|
part->insertTime(tick, len);
|
|
|
|
}
|
|
|
|
}
|
2012-06-28 10:03:19 +02:00
|
|
|
|
Lyrics multi-system melisma and dashes
Implements melisma and dash lines for lyrics spanning several systems.
The melisma and dash line is based on the `SLine` class and its segments on the `LineSegment` class. Both the whole line and its segments are not selectable, marked as generated and not saved in the score file, which is not changed in any way.
For very wide dash segments, several dashes are drawn; the distance between the dashes is not configurable.
Lyrics layout code in `Measure` class and in `layout.cpp` file has been commented out as the lyrics line layout is all contained in the lyrics.cpp file
The line is registered with the `Score` (to have its layout delayed until all elements are positioned) with a mechanism similar to other `Spanner`'s, but in a different container (`_unmanagedSpanner`), as the owning `Lyrics` should decide when create, register, unregister and delete its line.
The line segments are registered with the `System` they belong to (to have them drawn), in the same way as other `Spanner`'s.
There is code for using the dash metrics of the lyrics font, but it is turned off via a conditional directive, as there does not seem to be a reliable way to determine the dash metrics; conventional values (determined by trials and errors and based on my taste!) are used when the conditional directive is off.
2015-01-11 10:11:44 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// addUnmanagedSpanner
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::addUnmanagedSpanner(Spanner* s)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_unmanagedSpanner.insert(s);
|
|
|
|
}
|
Lyrics multi-system melisma and dashes
Implements melisma and dash lines for lyrics spanning several systems.
The melisma and dash line is based on the `SLine` class and its segments on the `LineSegment` class. Both the whole line and its segments are not selectable, marked as generated and not saved in the score file, which is not changed in any way.
For very wide dash segments, several dashes are drawn; the distance between the dashes is not configurable.
Lyrics layout code in `Measure` class and in `layout.cpp` file has been commented out as the lyrics line layout is all contained in the lyrics.cpp file
The line is registered with the `Score` (to have its layout delayed until all elements are positioned) with a mechanism similar to other `Spanner`'s, but in a different container (`_unmanagedSpanner`), as the owning `Lyrics` should decide when create, register, unregister and delete its line.
The line segments are registered with the `System` they belong to (to have them drawn), in the same way as other `Spanner`'s.
There is code for using the dash metrics of the lyrics font, but it is turned off via a conditional directive, as there does not seem to be a reliable way to determine the dash metrics; conventional values (determined by trials and errors and based on my taste!) are used when the conditional directive is off.
2015-01-11 10:11:44 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// removeSpanner
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::removeUnmanagedSpanner(Spanner* s)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_unmanagedSpanner.erase(s);
|
|
|
|
}
|
Lyrics multi-system melisma and dashes
Implements melisma and dash lines for lyrics spanning several systems.
The melisma and dash line is based on the `SLine` class and its segments on the `LineSegment` class. Both the whole line and its segments are not selectable, marked as generated and not saved in the score file, which is not changed in any way.
For very wide dash segments, several dashes are drawn; the distance between the dashes is not configurable.
Lyrics layout code in `Measure` class and in `layout.cpp` file has been commented out as the lyrics line layout is all contained in the lyrics.cpp file
The line is registered with the `Score` (to have its layout delayed until all elements are positioned) with a mechanism similar to other `Spanner`'s, but in a different container (`_unmanagedSpanner`), as the owning `Lyrics` should decide when create, register, unregister and delete its line.
The line segments are registered with the `System` they belong to (to have them drawn), in the same way as other `Spanner`'s.
There is code for using the dash metrics of the lyrics font, but it is turned off via a conditional directive, as there does not seem to be a reliable way to determine the dash metrics; conventional values (determined by trials and errors and based on my taste!) are used when the conditional directive is off.
2015-01-11 10:11:44 +01:00
|
|
|
|
2013-08-22 18:29:25 +02:00
|
|
|
//---------------------------------------------------------
|
2013-10-18 12:21:01 +02:00
|
|
|
// setPos
|
2013-08-22 18:29:25 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-09-20 19:38:57 +02:00
|
|
|
void MasterScore::setPos(POS pos, Fraction tick)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (tick < Fraction(0,1)) {
|
|
|
|
tick = Fraction(0,1);
|
|
|
|
}
|
|
|
|
Q_ASSERT(tick <= lastMeasure()->endTick());
|
|
|
|
|
|
|
|
_pos[int(pos)] = tick;
|
|
|
|
// even though tick position might not have changed, layout might have
|
|
|
|
// so we should update cursor here
|
|
|
|
// however, we must be careful not to call setPos() again while handling posChanged, or recursion results
|
|
|
|
for (Score* s : scoreList()) {
|
|
|
|
emit s->posChanged(pos, unsigned(tick.ticks()));
|
|
|
|
}
|
|
|
|
}
|
2013-09-19 15:08:54 +02:00
|
|
|
|
2014-05-22 16:18:35 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// uniqueStaves
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QList<int> Score::uniqueStaves() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QList<int> sl;
|
|
|
|
|
|
|
|
for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) {
|
|
|
|
Staff* s = staff(staffIdx);
|
|
|
|
if (s->links()) {
|
|
|
|
bool alreadyInList = false;
|
|
|
|
for (int idx : sl) {
|
|
|
|
if (s->links()->contains(staff(idx))) {
|
|
|
|
alreadyInList = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (alreadyInList) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sl.append(staffIdx);
|
|
|
|
}
|
|
|
|
return sl;
|
|
|
|
}
|
2014-05-22 16:18:35 +02:00
|
|
|
|
2014-05-29 12:13:49 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// findCR
|
|
|
|
// find chord/rest <= tick in track
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
ChordRest* Score::findCR(Fraction tick, int track) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Measure* m = tick2measureMM(tick);
|
|
|
|
if (!m) {
|
|
|
|
qDebug("findCR: no measure for tick %d", tick.ticks());
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
// attach to first rest all spanner when mmRest
|
|
|
|
if (m->isMMRest()) {
|
|
|
|
tick = m->tick();
|
|
|
|
}
|
|
|
|
Segment* s = m->first(SegmentType::ChordRest);
|
|
|
|
for (Segment* ns = s;; ns = ns->next(SegmentType::ChordRest)) {
|
|
|
|
if (ns == 0 || ns->tick() > tick) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Element* el = ns->element(track);
|
|
|
|
if (el && el->isRest() && toRest(el)->isGap()) {
|
|
|
|
continue;
|
|
|
|
} else if (el) {
|
|
|
|
s = ns;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Element* el = s->element(track);
|
|
|
|
if (el && el->isRest() && toRest(el)->isGap()) {
|
|
|
|
s = 0;
|
|
|
|
}
|
|
|
|
if (s) {
|
|
|
|
return toChordRest(s->element(track));
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
2014-05-29 12:13:49 +02:00
|
|
|
|
2014-08-18 11:33:17 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// findCRinStaff
|
2015-08-12 20:07:29 +02:00
|
|
|
// find last chord/rest on staff that ends before tick
|
2014-08-18 11:33:17 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-30 15:13:54 +01:00
|
|
|
ChordRest* Score::findCRinStaff(const Fraction& tick, int staffIdx) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Fraction ptick = tick - Fraction::fromTicks(1);
|
|
|
|
Measure* m = tick2measureMM(ptick);
|
|
|
|
if (!m) {
|
|
|
|
qDebug("findCRinStaff: no measure for tick %d", ptick.ticks());
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
// attach to first rest all spanner when mmRest
|
|
|
|
if (m->isMMRest()) {
|
|
|
|
ptick = m->tick();
|
|
|
|
}
|
|
|
|
|
|
|
|
Segment* s = m->first(SegmentType::ChordRest);
|
|
|
|
int strack = staffIdx * VOICES;
|
|
|
|
int etrack = strack + VOICES;
|
|
|
|
int actualTrack = strack;
|
|
|
|
|
|
|
|
Fraction lastTick = Fraction(-1,1);
|
|
|
|
for (Segment* ns = s;; ns = ns->next(SegmentType::ChordRest)) {
|
|
|
|
if (ns == 0 || ns->tick() > ptick) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// found a segment; now find longest cr on this staff that does not overlap tick
|
|
|
|
for (int t = strack; t < etrack; ++t) {
|
|
|
|
ChordRest* cr = toChordRest(ns->element(t));
|
|
|
|
if (cr) {
|
|
|
|
Fraction endTick = cr->tick() + cr->actualTicks();
|
|
|
|
if (endTick >= lastTick && endTick <= tick) {
|
|
|
|
s = ns;
|
|
|
|
actualTrack = t;
|
|
|
|
lastTick = endTick;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (s) {
|
|
|
|
return toChordRest(s->element(actualTrack));
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2014-08-18 11:33:17 +02:00
|
|
|
|
2014-08-16 17:01:41 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setSoloMute
|
|
|
|
// called once at opening file, adds soloMute marks
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-03-11 12:18:46 +01:00
|
|
|
void MasterScore::setSoloMute()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
for (unsigned i = 0; i < _midiMapping.size(); i++) {
|
|
|
|
Channel* b = _midiMapping[i].articulation();
|
|
|
|
if (b->solo()) {
|
|
|
|
b->setSoloMute(false);
|
|
|
|
for (unsigned j = 0; j < _midiMapping.size(); j++) {
|
|
|
|
Channel* a = _midiMapping[j].articulation();
|
|
|
|
bool sameMidiMapping = _midiMapping[i].port() == _midiMapping[j].port()
|
|
|
|
&& _midiMapping[i].channel() == _midiMapping[j].channel();
|
|
|
|
a->setSoloMute((i != j && !a->solo() && !sameMidiMapping));
|
|
|
|
a->setSolo(i == j || a->solo() || sameMidiMapping);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-08-16 17:01:41 +02:00
|
|
|
|
2014-11-01 10:52:35 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setImportedFilePath
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setImportedFilePath(const QString& filePath)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_importedFilePath = filePath;
|
|
|
|
}
|
2014-11-01 10:52:35 +01:00
|
|
|
|
2014-12-28 18:20:12 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// nmeasure
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-01-22 10:03:40 +01:00
|
|
|
int Score::nmeasures() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
for (const Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
2014-12-28 18:20:12 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// hasLyrics
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::hasLyrics()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
SegmentType st = SegmentType::ChordRest;
|
|
|
|
for (Segment* seg = firstMeasure()->first(st); seg; seg = seg->next1(st)) {
|
|
|
|
for (int i = 0; i < ntracks(); ++i) {
|
|
|
|
ChordRest* cr = toChordRest(seg->element(i));
|
|
|
|
if (cr && !cr->lyrics().empty()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2014-12-28 18:20:12 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// hasHarmonies
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::hasHarmonies()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
SegmentType st = SegmentType::ChordRest;
|
|
|
|
for (Segment* seg = firstMeasure()->first(st); seg; seg = seg->next1(st)) {
|
|
|
|
for (Element* e : seg->annotations()) {
|
|
|
|
if (e->type() == ElementType::HARMONY) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2014-12-28 18:20:12 +01:00
|
|
|
|
2015-07-02 18:07:21 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// lyricCount
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Score::lyricCount()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
size_t count = 0;
|
|
|
|
SegmentType st = SegmentType::ChordRest;
|
|
|
|
for (Segment* seg = firstMeasure()->first(st); seg; seg = seg->next1(st)) {
|
|
|
|
for (int i = 0; i < ntracks(); ++i) {
|
|
|
|
ChordRest* cr = toChordRest(seg->element(i));
|
|
|
|
if (cr) {
|
|
|
|
count += cr->lyrics().size();
|
2015-07-02 18:07:21 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return int(count);
|
|
|
|
}
|
2015-07-02 18:07:21 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// harmonyCount
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Score::harmonyCount()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
SegmentType st = SegmentType::ChordRest;
|
|
|
|
for (Segment* seg = firstMeasure()->first(st); seg; seg = seg->next1(st)) {
|
|
|
|
for (Element* e : seg->annotations()) {
|
|
|
|
if (e->type() == ElementType::HARMONY) {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
2015-07-02 18:07:21 +02:00
|
|
|
|
2016-08-17 12:52:35 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// extractLyrics
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2015-07-02 18:07:21 +02:00
|
|
|
QString Score::extractLyrics()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QString result;
|
|
|
|
masterScore()->setExpandRepeats(true);
|
|
|
|
SegmentType st = SegmentType::ChordRest;
|
|
|
|
for (int track = 0; track < ntracks(); track += VOICES) {
|
|
|
|
bool found = false;
|
|
|
|
size_t maxLyrics = 1;
|
|
|
|
const RepeatList& rlist = repeatList();
|
|
|
|
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
|
|
|
|
m->setPlaybackCount(0);
|
|
|
|
}
|
|
|
|
// follow the repeat segments
|
|
|
|
for (const RepeatSegment* rs : rlist) {
|
|
|
|
Fraction startTick = Fraction::fromTicks(rs->tick);
|
|
|
|
Fraction endTick = startTick + Fraction::fromTicks(rs->len());
|
|
|
|
for (Measure* m = tick2measure(startTick); m; m = m->nextMeasure()) {
|
|
|
|
int playCount = m->playbackCount();
|
|
|
|
for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
|
|
|
|
// consider voice 1 only
|
|
|
|
ChordRest* cr = toChordRest(seg->element(track));
|
|
|
|
if (!cr || cr->lyrics().empty()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (cr->lyrics().size() > maxLyrics) {
|
|
|
|
maxLyrics = cr->lyrics().size();
|
|
|
|
}
|
|
|
|
if (playCount >= int(cr->lyrics().size())) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Lyrics* l = cr->lyrics(playCount, Placement::BELOW); // TODO: ABOVE
|
|
|
|
if (!l) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
found = true;
|
|
|
|
QString lyric = l->plainText().trimmed();
|
|
|
|
if (l->syllabic() == Lyrics::Syllabic::SINGLE || l->syllabic() == Lyrics::Syllabic::END) {
|
|
|
|
result += lyric + " ";
|
|
|
|
} else if (l->syllabic() == Lyrics::Syllabic::BEGIN || l->syllabic() == Lyrics::Syllabic::MIDDLE) {
|
|
|
|
result += lyric;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m->setPlaybackCount(m->playbackCount() + 1);
|
|
|
|
if (m->endTick() >= endTick) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// consider remaining lyrics
|
|
|
|
for (unsigned lyricsNumber = 0; lyricsNumber < maxLyrics; lyricsNumber++) {
|
2015-07-02 18:07:21 +02:00
|
|
|
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
|
2020-05-26 15:54:26 +02:00
|
|
|
unsigned playCount = m->playbackCount();
|
|
|
|
if (lyricsNumber >= playCount) {
|
|
|
|
for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
|
|
|
|
// consider voice 1 only
|
|
|
|
ChordRest* cr = toChordRest(seg->element(track));
|
|
|
|
if (!cr || cr->lyrics().empty()) {
|
|
|
|
continue;
|
2015-07-02 18:07:21 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
if (cr->lyrics().size() > maxLyrics) {
|
|
|
|
maxLyrics = cr->lyrics().size();
|
2015-07-02 18:07:21 +02:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
if (lyricsNumber >= cr->lyrics().size()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Lyrics* l = cr->lyrics(lyricsNumber, Placement::BELOW); // TODO
|
|
|
|
if (!l) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
found = true;
|
|
|
|
QString lyric = l->plainText().trimmed();
|
|
|
|
if (l->syllabic() == Lyrics::Syllabic::SINGLE || l->syllabic() == Lyrics::Syllabic::END) {
|
|
|
|
result += lyric + " ";
|
|
|
|
} else if (l->syllabic() == Lyrics::Syllabic::BEGIN
|
|
|
|
|| l->syllabic() == Lyrics:: Syllabic::MIDDLE) {
|
|
|
|
result += lyric;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found) {
|
|
|
|
result += "\n\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result.trimmed();
|
|
|
|
}
|
2015-07-02 18:07:21 +02:00
|
|
|
|
2014-12-28 18:20:12 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// keysig
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Score::keysig()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Key result = Key::C;
|
|
|
|
for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) {
|
|
|
|
Staff* st = staff(staffIdx);
|
|
|
|
constexpr Fraction t(0,1);
|
|
|
|
Key key = st->key(t);
|
|
|
|
if (st->staffType(t)->group() == StaffGroup::PERCUSSION || st->keySigEvent(t).custom()
|
|
|
|
|| st->keySigEvent(t).isAtonal()) { // ignore percussion and custom / atonal key
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
result = key;
|
|
|
|
int diff = st->part()->instrument()->transpose().chromatic;
|
|
|
|
if (!styleB(Sid::concertPitch) && diff) {
|
|
|
|
result = transposeKey(key, diff, st->part()->preferSharpFlat());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return int(result);
|
|
|
|
}
|
2014-12-28 18:20:12 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// duration
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Score::duration()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
masterScore()->setExpandRepeats(true);
|
|
|
|
const RepeatList& rl = repeatList();
|
|
|
|
if (rl.empty()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
const RepeatSegment* rs = rl.last();
|
|
|
|
return lrint(utick2utime(rs->utick + rs->len()));
|
|
|
|
}
|
2014-12-28 18:20:12 +01:00
|
|
|
|
2014-11-15 16:29:48 +01:00
|
|
|
//---------------------------------------------------------
|
2014-12-09 08:45:30 +01:00
|
|
|
// createRehearsalMarkText
|
2014-11-15 16:29:48 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2014-12-09 08:45:30 +01:00
|
|
|
QString Score::createRehearsalMarkText(RehearsalMark* current) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
Fraction tick = current->segment()->tick();
|
|
|
|
RehearsalMark* before = 0;
|
|
|
|
RehearsalMark* after = 0;
|
|
|
|
for (Segment* s = firstSegment(SegmentType::All); s; s = s->next1()) {
|
|
|
|
for (Element* e : s->annotations()) {
|
|
|
|
if (e && e->type() == ElementType::REHEARSAL_MARK) {
|
|
|
|
if (s->tick() < tick) {
|
|
|
|
before = toRehearsalMark(e);
|
|
|
|
} else if (s->tick() > tick) {
|
|
|
|
after = toRehearsalMark(e);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (after) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QString s = "A";
|
|
|
|
QString s1 = before ? before->xmlText() : "";
|
|
|
|
QString s2 = after ? after->xmlText() : "";
|
|
|
|
if (s1.isEmpty()) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
s = nextRehearsalMarkText(before, current); // try to sequence
|
|
|
|
if (s == current->xmlText()) {
|
|
|
|
// no sequence detected (or current happens to be correct)
|
|
|
|
return s;
|
|
|
|
} else if (s == s2) {
|
|
|
|
// next in sequence already present
|
|
|
|
if (s1[0].isLetter()) {
|
|
|
|
if (s1.size() == 2) {
|
|
|
|
s = s1[0] + QChar::fromLatin1(s1[1].toLatin1() + 1); // BB, BC, CC
|
|
|
|
} else {
|
|
|
|
s = s1 + QChar::fromLatin1('1'); // B, B1, C
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
s = s1 + QChar::fromLatin1('A'); // 2, 2A, 3
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
2014-11-15 16:29:48 +01:00
|
|
|
|
2014-12-09 08:45:30 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// nextRehearsalMarkText
|
|
|
|
// finds next rehearsal in sequence established by previous
|
|
|
|
// Alphabetic sequences:
|
|
|
|
// A, B, ..., Y, Z, AA, BB, ..., YY, ZZ
|
|
|
|
// a, b, ..., y, z, aa, bb, ..., yy, zz
|
|
|
|
// Numeric sequences:
|
|
|
|
// 1, 2, 3, ...
|
|
|
|
// If number of previous rehearsal mark matches measure number, assume use of measure numbers throughout
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QString Score::nextRehearsalMarkText(RehearsalMark* previous, RehearsalMark* current) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QString previousText = previous->xmlText();
|
|
|
|
QString fallback = current ? current->xmlText() : previousText + "'";
|
|
|
|
|
|
|
|
if (previousText.length() == 1 && previousText[0].isLetter()) {
|
|
|
|
// single letter sequence
|
|
|
|
if (previousText == "Z") {
|
|
|
|
return "AA";
|
|
|
|
} else if (previousText == "z") {
|
|
|
|
return "aa";
|
|
|
|
} else {
|
|
|
|
return QChar::fromLatin1(previousText[0].toLatin1() + 1);
|
|
|
|
}
|
|
|
|
} else if (previousText.length() == 2 && previousText[0].isLetter() && previousText[1].isLetter()) {
|
|
|
|
// double letter sequence
|
|
|
|
if (previousText[0] == previousText[1]) {
|
|
|
|
// repeated letter sequence
|
|
|
|
if (previousText.toUpper() != "ZZ") {
|
|
|
|
QString c = QChar::fromLatin1(previousText[0].toLatin1() + 1);
|
|
|
|
return c + c;
|
|
|
|
} else {
|
|
|
|
return fallback;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fallback;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// try to interpret as number
|
|
|
|
bool ok;
|
|
|
|
int n = previousText.toInt(&ok);
|
|
|
|
if (!ok) {
|
|
|
|
return fallback;
|
|
|
|
} else if (current && n == previous->segment()->measure()->no() + 1) {
|
|
|
|
// use measure number
|
|
|
|
n = current->segment()->measure()->no() + 1;
|
|
|
|
return QString("%1").arg(n);
|
|
|
|
} else {
|
|
|
|
// use number sequence
|
|
|
|
n = previousText.toInt() + 1;
|
|
|
|
return QString("%1").arg(n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-12-09 08:45:30 +01:00
|
|
|
|
2015-02-13 06:57:37 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// changeVoice
|
|
|
|
// moves selected notes into specified voice if possible
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::changeVoice(int voice)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
startCmd();
|
|
|
|
QList<Element*> el;
|
|
|
|
QList<Element*> oel = selection().elements(); // make copy
|
|
|
|
for (Element* e : oel) {
|
|
|
|
if (e->type() == ElementType::NOTE) {
|
|
|
|
Note* note = toNote(e);
|
|
|
|
Chord* chord = note->chord();
|
|
|
|
|
|
|
|
// move grace notes with main chord only
|
|
|
|
if (chord->isGrace()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chord->voice() != voice) {
|
|
|
|
Segment* s = chord->segment();
|
|
|
|
Measure* m = s->measure();
|
|
|
|
size_t notes = chord->notes().size();
|
|
|
|
int dstTrack = chord->staffIdx() * VOICES + voice;
|
|
|
|
ChordRest* dstCR = toChordRest(s->element(dstTrack));
|
|
|
|
Chord* dstChord = nullptr;
|
|
|
|
|
|
|
|
if (excerpt() && excerpt()->tracks().key(dstTrack, -1) == -1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set up destination chord
|
|
|
|
|
|
|
|
if (dstCR && dstCR->type() == ElementType::CHORD && dstCR->globalTicks() == chord->globalTicks()) {
|
|
|
|
// existing chord in destination with correct duration;
|
|
|
|
// can simply move note in
|
|
|
|
dstChord = toChord(dstCR);
|
|
|
|
} else if (dstCR && dstCR->type() == ElementType::REST
|
|
|
|
&& dstCR->globalTicks() == chord->globalTicks()) {
|
|
|
|
// existing rest in destination with correct duration;
|
|
|
|
// replace with chord, then move note in
|
|
|
|
// this case allows for tuplets, unlike the more general case below
|
|
|
|
dstChord = new Chord(this);
|
|
|
|
dstChord->setTrack(dstTrack);
|
|
|
|
dstChord->setDurationType(chord->durationType());
|
|
|
|
dstChord->setTicks(chord->ticks());
|
|
|
|
dstChord->setTuplet(dstCR->tuplet());
|
|
|
|
dstChord->setParent(s);
|
|
|
|
undoRemoveElement(dstCR);
|
|
|
|
} else if (!chord->tuplet()) {
|
|
|
|
// rests or gap in destination
|
|
|
|
// insert new chord if the rests / gap are long enough
|
|
|
|
// then move note in
|
|
|
|
ChordRest* pcr = nullptr;
|
|
|
|
ChordRest* ncr = nullptr;
|
|
|
|
for (Segment* s2 = m->first(SegmentType::ChordRest); s2; s2 = s2->next()) {
|
|
|
|
if (s2->segmentType() != SegmentType::ChordRest) {
|
|
|
|
continue;
|
2015-02-13 06:57:37 +01:00
|
|
|
}
|
2020-05-26 15:54:26 +02:00
|
|
|
ChordRest* cr2 = toChordRest(s2->element(dstTrack));
|
|
|
|
if (!cr2 || cr2->type() == ElementType::REST) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (s2->tick() < s->tick()) {
|
|
|
|
pcr = cr2;
|
|
|
|
continue;
|
|
|
|
} else if (s2->tick() >= s->tick()) {
|
|
|
|
ncr = cr2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Fraction gapStart = pcr ? pcr->tick() + pcr->actualTicks() : m->tick();
|
|
|
|
Fraction gapEnd = ncr ? ncr->tick() : m->tick() + m->ticks();
|
|
|
|
if (gapStart <= s->tick() && gapEnd >= s->tick() + chord->actualTicks()) {
|
|
|
|
// big enough gap found
|
|
|
|
dstChord = new Chord(this);
|
|
|
|
dstChord->setTrack(dstTrack);
|
|
|
|
dstChord->setDurationType(chord->durationType());
|
|
|
|
dstChord->setTicks(chord->ticks());
|
|
|
|
dstChord->setParent(s);
|
|
|
|
// makeGapVoice will not back-fill an empty voice
|
|
|
|
if (voice && !dstCR) {
|
|
|
|
expandVoice(s, /*m->first(SegmentType::ChordRest,*/ dstTrack);
|
|
|
|
}
|
|
|
|
makeGapVoice(s, dstTrack, chord->actualTicks(), s->tick());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// move note to destination chord
|
|
|
|
if (dstChord) {
|
|
|
|
// create & add new note
|
|
|
|
Note* newNote = new Note(*note);
|
|
|
|
newNote->setSelected(false);
|
|
|
|
newNote->setParent(dstChord);
|
|
|
|
undoAddElement(newNote);
|
|
|
|
el.append(newNote);
|
|
|
|
// add new chord if one was created
|
|
|
|
if (dstChord != dstCR) {
|
|
|
|
undoAddCR(dstChord, m, s->tick());
|
|
|
|
}
|
|
|
|
// reconnect the tie to this note, if any
|
|
|
|
Tie* tie = note->tieBack();
|
|
|
|
if (tie) {
|
|
|
|
undoChangeSpannerElements(tie, tie->startNote(), newNote);
|
|
|
|
}
|
|
|
|
// reconnect the tie from this note, if any
|
|
|
|
tie = note->tieFor();
|
|
|
|
if (tie) {
|
|
|
|
undoChangeSpannerElements(tie, newNote, tie->endNote());
|
|
|
|
}
|
|
|
|
// remove original note
|
|
|
|
if (notes > 1) {
|
|
|
|
undoRemoveElement(note);
|
|
|
|
} else if (notes == 1) {
|
|
|
|
// create rest to leave behind
|
|
|
|
Rest* r = new Rest(this);
|
|
|
|
r->setTrack(chord->track());
|
|
|
|
r->setDurationType(chord->durationType());
|
|
|
|
r->setTicks(chord->ticks());
|
|
|
|
r->setTuplet(chord->tuplet());
|
|
|
|
r->setParent(s);
|
|
|
|
// if there were grace notes, move them
|
|
|
|
for (Chord* gc : chord->graceNotes()) {
|
|
|
|
Chord* ngc = new Chord(*gc);
|
|
|
|
undoRemoveElement(gc);
|
|
|
|
ngc->setParent(dstChord);
|
|
|
|
ngc->setTrack(dstChord->track());
|
|
|
|
undoAddElement(ngc);
|
|
|
|
}
|
|
|
|
// remove chord, replace with rest
|
|
|
|
undoRemoveElement(chord);
|
|
|
|
undoAddCR(r, m, s->tick());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!el.empty()) {
|
|
|
|
selection().clear();
|
|
|
|
}
|
|
|
|
for (Element* e : el) {
|
|
|
|
select(e, SelectType::ADD, -1);
|
|
|
|
}
|
|
|
|
setLayoutAll();
|
|
|
|
endCmd();
|
|
|
|
}
|
2015-02-13 06:57:37 +01:00
|
|
|
|
2017-01-23 21:53:51 +01:00
|
|
|
#if 0
|
2015-02-20 22:30:10 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// cropPage - crop a single page score to the content
|
|
|
|
/// margins will be applied on the 4 sides
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::cropPage(qreal margins)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (npages() == 1) {
|
|
|
|
Page* page = pages()[0];
|
|
|
|
if (page) {
|
|
|
|
QRectF ttbox = page->tbbox();
|
|
|
|
|
|
|
|
qreal margin = margins / INCH;
|
|
|
|
f.setSize(QSizeF((ttbox.width() / DPI) + 2 * margin, (ttbox.height() / DPI) + 2 * margin));
|
|
|
|
|
|
|
|
qreal offset = curFormat->oddLeftMargin() - ttbox.x() / DPI;
|
|
|
|
if (offset < 0) {
|
|
|
|
offset = 0.0;
|
|
|
|
}
|
|
|
|
f.setOddLeftMargin(margin + offset);
|
|
|
|
f.setEvenLeftMargin(margin + offset);
|
|
|
|
f.setOddBottomMargin(margin);
|
|
|
|
f.setOddTopMargin(margin);
|
|
|
|
f.setEvenBottomMargin(margin);
|
|
|
|
f.setEvenTopMargin(margin);
|
|
|
|
|
|
|
|
undoChangePageFormat(&f, spatium(), pageNumberOffset());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-23 21:53:51 +01:00
|
|
|
#endif
|
2015-02-20 22:30:10 +01:00
|
|
|
|
2015-10-01 15:03:32 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// getProperty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-08-14 10:03:49 +02:00
|
|
|
QVariant Score::getProperty(Pid /*id*/) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
qDebug("Score::getProperty: unhandled id");
|
|
|
|
return QVariant();
|
|
|
|
}
|
2015-10-01 15:03:32 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setProperty
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-08-14 10:03:49 +02:00
|
|
|
bool Score::setProperty(Pid /*id*/, const QVariant& /*v*/)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
qDebug("Score::setProperty: unhandled id");
|
|
|
|
setLayoutAll();
|
|
|
|
return true;
|
|
|
|
}
|
2015-10-01 15:03:32 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// propertyDefault
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-08-14 10:03:49 +02:00
|
|
|
QVariant Score::propertyDefault(Pid /*id*/) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return QVariant();
|
|
|
|
}
|
2015-10-01 15:03:32 +02:00
|
|
|
|
2016-03-02 13:20:19 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setStyle
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::setStyle(const MStyle& s)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
style() = s;
|
|
|
|
}
|
2016-03-10 10:41:31 +01:00
|
|
|
|
2018-11-01 19:15:36 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// getTextStyleUserName
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
QString Score::getTextStyleUserName(Tid tid)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QString name = "";
|
|
|
|
if (int(tid) >= int(Tid::USER1) && int(tid) <= int(Tid::USER12)) {
|
|
|
|
int idx = int(tid) - int(Tid::USER1);
|
|
|
|
Sid sid[] = { Sid::user1Name, Sid::user2Name, Sid::user3Name, Sid::user4Name, Sid::user5Name, Sid::user6Name,
|
|
|
|
Sid::user7Name, Sid::user8Name, Sid::user9Name, Sid::user10Name, Sid::user11Name,
|
|
|
|
Sid::user12Name };
|
|
|
|
name = styleSt(sid[idx]);
|
|
|
|
}
|
|
|
|
if (name == "") {
|
|
|
|
name = textStyleUserName(tid);
|
|
|
|
}
|
|
|
|
return name;
|
|
|
|
}
|
2018-11-01 19:15:36 +01:00
|
|
|
|
2016-03-10 10:41:31 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// MasterScore
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
MasterScore::MasterScore() :
|
|
|
|
Score()
|
|
|
|
{
|
|
|
|
_tempomap = new TempoMap;
|
|
|
|
_sigmap = new TimeSigMap();
|
|
|
|
_repeatList = new RepeatList(this);
|
|
|
|
_revisions = new Revisions;
|
|
|
|
setMasterScore(this);
|
2016-03-10 10:41:31 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
_pos[int(POS::CURRENT)] = Fraction(0,1);
|
|
|
|
_pos[int(POS::LEFT)] = Fraction(0,1);
|
|
|
|
_pos[int(POS::RIGHT)] = Fraction(0,1);
|
2019-09-20 19:38:57 +02:00
|
|
|
|
2016-03-10 10:41:31 +01:00
|
|
|
#if defined(Q_OS_WIN)
|
2020-05-26 15:54:26 +02:00
|
|
|
metaTags().insert("platform", "Microsoft Windows");
|
2016-03-10 10:41:31 +01:00
|
|
|
#elif defined(Q_OS_MAC)
|
2020-05-26 15:54:26 +02:00
|
|
|
metaTags().insert("platform", "Apple Macintosh");
|
2016-03-10 10:41:31 +01:00
|
|
|
#elif defined(Q_OS_LINUX)
|
2020-05-26 15:54:26 +02:00
|
|
|
metaTags().insert("platform", "Linux");
|
2016-03-10 10:41:31 +01:00
|
|
|
#else
|
2020-05-26 15:54:26 +02:00
|
|
|
metaTags().insert("platform", "Unknown");
|
2016-03-10 10:41:31 +01:00
|
|
|
#endif
|
2020-05-26 15:54:26 +02:00
|
|
|
metaTags().insert("movementNumber", "");
|
|
|
|
metaTags().insert("movementTitle", "");
|
|
|
|
metaTags().insert("workNumber", "");
|
|
|
|
metaTags().insert("workTitle", "");
|
|
|
|
metaTags().insert("arranger", "");
|
|
|
|
metaTags().insert("composer", "");
|
|
|
|
metaTags().insert("lyricist", "");
|
|
|
|
metaTags().insert("poet", "");
|
|
|
|
metaTags().insert("translator", "");
|
|
|
|
metaTags().insert("source", "");
|
|
|
|
metaTags().insert("copyright", "");
|
|
|
|
metaTags().insert("creationDate", QDate::currentDate().toString(Qt::ISODate));
|
|
|
|
}
|
|
|
|
|
|
|
|
MasterScore::MasterScore(const MStyle& s) :
|
|
|
|
MasterScore{}
|
|
|
|
{
|
|
|
|
_movements = new Movements;
|
|
|
|
_movements->push_back(this);
|
|
|
|
setStyle(s);
|
|
|
|
}
|
2016-03-10 10:41:31 +01:00
|
|
|
|
|
|
|
MasterScore::~MasterScore()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
delete _revisions;
|
|
|
|
delete _repeatList;
|
|
|
|
delete _sigmap;
|
|
|
|
delete _tempomap;
|
|
|
|
qDeleteAll(_excerpts);
|
|
|
|
}
|
2016-03-10 10:41:31 +01:00
|
|
|
|
2017-01-31 12:21:44 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setMovements
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MasterScore::setMovements(Movements* m)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_movements = m;
|
|
|
|
if (_movements) {
|
|
|
|
_movements->push_back(this);
|
|
|
|
}
|
|
|
|
}
|
2017-01-31 12:21:44 +01:00
|
|
|
|
2016-03-10 10:41:31 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// isSavable
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool MasterScore::isSavable() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
// TODO: check if file can be created if it does not exist
|
|
|
|
return fileInfo()->isWritable() || !fileInfo()->exists();
|
|
|
|
}
|
2016-03-10 10:41:31 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setTempomap
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MasterScore::setTempomap(TempoMap* tm)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
delete _tempomap;
|
|
|
|
_tempomap = tm;
|
|
|
|
}
|
2016-03-10 10:41:31 +01:00
|
|
|
|
2016-10-10 18:37:28 +02:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// removeOmr
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MasterScore::removeOmr()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_showOmr = false;
|
2016-10-10 18:37:28 +02:00
|
|
|
#ifdef OMR
|
2020-05-26 15:54:26 +02:00
|
|
|
delete _omr;
|
2016-10-10 18:37:28 +02:00
|
|
|
#endif
|
2020-05-26 15:54:26 +02:00
|
|
|
_omr = 0;
|
|
|
|
}
|
2016-10-10 18:37:28 +02:00
|
|
|
|
2016-03-10 10:41:31 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setName
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-10-10 18:37:28 +02:00
|
|
|
void MasterScore::setName(const QString& ss)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
QString s(ss);
|
|
|
|
s.replace('/', '_'); // for sanity
|
|
|
|
if (!(s.endsWith(".mscz") || s.endsWith(".mscx"))) {
|
|
|
|
s += ".mscz";
|
|
|
|
}
|
|
|
|
info.setFile(s);
|
|
|
|
}
|
2016-03-10 10:41:31 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2016-10-10 18:37:28 +02:00
|
|
|
// title
|
2016-03-10 10:41:31 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-10-10 18:37:28 +02:00
|
|
|
QString MasterScore::title() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return fileInfo()->completeBaseName();
|
|
|
|
}
|
2016-10-10 18:37:28 +02:00
|
|
|
|
|
|
|
QString Score::title() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return _excerpt->title();
|
|
|
|
}
|
2016-03-10 10:41:31 +01:00
|
|
|
|
2016-03-18 09:29:16 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// addRefresh
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void Score::addRefresh(const QRectF& r)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_updateState.refresh |= r;
|
|
|
|
cmdState().setUpdateMode(UpdateMode::Update);
|
|
|
|
}
|
2016-09-28 21:13:05 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// staffIdx
|
|
|
|
//
|
|
|
|
/// Return index for the first staff of \a part.
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
int Score::staffIdx(const Part* part) const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
int idx = 0;
|
|
|
|
for (Part* p : _parts) {
|
|
|
|
if (p == part) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
idx += p->nstaves();
|
|
|
|
}
|
|
|
|
return idx;
|
|
|
|
}
|
2016-09-28 21:13:05 +02:00
|
|
|
|
2017-01-05 11:23:47 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setUpdateAll
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MasterScore::setUpdateAll()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_cmdState.setUpdateMode(UpdateMode::UpdateAll);
|
|
|
|
}
|
2017-01-05 11:23:47 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setLayoutAll
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-10-24 15:49:23 +02:00
|
|
|
void MasterScore::setLayoutAll(int staff, const Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
_cmdState.setTick(Fraction(0,1));
|
|
|
|
_cmdState.setTick(measures()->last() ? measures()->last()->endTick() : Fraction(0,1));
|
|
|
|
|
|
|
|
if (e && e->score() == this) {
|
|
|
|
// TODO: map staff number properly
|
|
|
|
const int startStaff = staff == -1 ? 0 : staff;
|
|
|
|
const int endStaff = staff == -1 ? (nstaves() - 1) : staff;
|
|
|
|
_cmdState.setStaff(startStaff);
|
|
|
|
_cmdState.setStaff(endStaff);
|
|
|
|
|
|
|
|
_cmdState.setElement(e);
|
|
|
|
}
|
|
|
|
}
|
2019-10-24 15:49:23 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// setLayout
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MasterScore::setLayout(const Fraction& t, int staff, const Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (t >= Fraction(0,1)) {
|
|
|
|
_cmdState.setTick(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e && e->score() == this) {
|
|
|
|
// TODO: map staff number properly
|
|
|
|
_cmdState.setStaff(staff);
|
|
|
|
_cmdState.setElement(e);
|
|
|
|
}
|
|
|
|
}
|
2019-10-24 15:49:23 +02:00
|
|
|
|
|
|
|
void MasterScore::setLayout(const Fraction& tick1, const Fraction& tick2, int staff1, int staff2, const Element* e)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (tick1 >= Fraction(0,1)) {
|
|
|
|
_cmdState.setTick(tick1);
|
|
|
|
}
|
|
|
|
if (tick2 >= Fraction(0,1)) {
|
|
|
|
_cmdState.setTick(tick2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e && e->score() == this) {
|
|
|
|
// TODO: map staff number properly
|
|
|
|
_cmdState.setStaff(staff1);
|
|
|
|
_cmdState.setStaff(staff2);
|
|
|
|
|
|
|
|
_cmdState.setElement(e);
|
|
|
|
}
|
|
|
|
}
|
2017-01-05 11:23:47 +01:00
|
|
|
|
2019-02-08 13:20:56 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// setPlaybackScore
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
void MasterScore::setPlaybackScore(Score* score)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (_playbackScore == score) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_playbackScore = score;
|
|
|
|
_playbackSettingsLinks.clear();
|
|
|
|
|
|
|
|
if (!_playbackScore) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (MidiMapping& mm : _midiMapping) {
|
|
|
|
mm.articulation()->setSoloMute(true);
|
|
|
|
}
|
|
|
|
for (Part* part : score->parts()) {
|
|
|
|
for (auto& i : *part->instruments()) {
|
|
|
|
Instrument* instr = i.second;
|
|
|
|
for (Channel* ch : instr->channel()) {
|
|
|
|
Channel* pChannel = playbackChannel(ch);
|
|
|
|
Q_ASSERT(pChannel);
|
|
|
|
if (!pChannel) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
_playbackSettingsLinks.emplace_back(pChannel, ch, /* excerpt */ true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-08 13:20:56 +01:00
|
|
|
|
2018-12-22 11:43:23 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// updateExpressive
|
|
|
|
// change patches to their expressive equivalent or vica versa, if possible
|
|
|
|
// This works only with MuseScore general soundfont
|
|
|
|
//
|
|
|
|
// The first version of the function decides whether to make patches expressive
|
|
|
|
// or not, based on the synth settings. The second will switch patches based on
|
|
|
|
// the value of the expressive parameter.
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-05-06 15:23:33 +02:00
|
|
|
void MasterScore::updateExpressive(Synthesizer* synth)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
SynthesizerState s = synthesizerState();
|
|
|
|
SynthesizerGroup g = s.group("master");
|
|
|
|
|
|
|
|
int method = 1;
|
|
|
|
for (IdValue idVal : g) {
|
|
|
|
if (idVal.id == 4) {
|
|
|
|
method = idVal.data.toInt();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-12-22 11:43:23 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
updateExpressive(synth, (method != 0));
|
|
|
|
}
|
2018-12-22 11:43:23 +01:00
|
|
|
|
2019-05-06 15:23:33 +02:00
|
|
|
void MasterScore::updateExpressive(Synthesizer* synth, bool expressive, bool force /* = false */)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
if (!synth) {
|
|
|
|
return;
|
|
|
|
}
|
2018-12-22 11:43:23 +01:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
if (!force) {
|
|
|
|
SynthesizerState s = synthesizerState();
|
|
|
|
SynthesizerGroup g = s.group("master");
|
2019-04-11 16:12:20 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
for (IdValue idVal : g) {
|
|
|
|
if (idVal.id == 4) {
|
|
|
|
int method = idVal.data.toInt();
|
|
|
|
if (expressive == (method == 0)) {
|
|
|
|
return; // method and expression change don't match, so don't switch}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Part* p : parts()) {
|
|
|
|
const InstrumentList* il = p->instruments();
|
|
|
|
for (auto it = il->begin(); it != il->end(); it++) {
|
|
|
|
Instrument* i = it->second;
|
|
|
|
i->switchExpressive(this, synth, expressive, force);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-12 22:46:40 +02:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// rebuildAndUpdateExpressive
|
|
|
|
// implicitly rebuild midi mappings as well. Should be preferred over
|
|
|
|
// just updateExpressive, in most cases.
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2019-05-06 15:23:33 +02:00
|
|
|
void MasterScore::rebuildAndUpdateExpressive(Synthesizer* synth)
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
// Rebuild midi mappings to make sure we have playback channels
|
|
|
|
rebuildMidiMapping();
|
2019-04-12 22:46:40 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
updateExpressive(synth);
|
2019-04-12 22:46:40 +02:00
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
// Rebuild midi mappings again to be safe
|
|
|
|
rebuildMidiMapping();
|
|
|
|
}
|
2018-12-22 11:43:23 +01:00
|
|
|
|
2017-01-05 11:23:47 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// isTopScore
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
bool Score::isTopScore() const
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
return !(isMaster() && static_cast<const MasterScore*>(this)->prev());
|
|
|
|
}
|
2017-01-05 11:23:47 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
// Movements
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2020-05-26 15:54:26 +02:00
|
|
|
Movements::Movements() :
|
|
|
|
std::vector<MasterScore*>(), _headersText(MAX_HEADERS, nullptr), _footersText(MAX_FOOTERS, nullptr)
|
|
|
|
{
|
|
|
|
_undo = new UndoStack();
|
|
|
|
}
|
2017-01-05 11:23:47 +01:00
|
|
|
|
|
|
|
Movements::~Movements()
|
2020-05-26 15:54:26 +02:00
|
|
|
{
|
|
|
|
qDeleteAll(_pages);
|
|
|
|
delete _undo;
|
|
|
|
}
|
2017-01-05 11:23:47 +01:00
|
|
|
|
2018-11-08 15:45:14 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
// ScoreLoad::_loading
|
2018-11-29 13:35:34 +01:00
|
|
|
// If the _loading > 0 then pushes and pops to
|
2018-11-08 15:45:14 +01:00
|
|
|
// the undo stack do not emit a warning.
|
|
|
|
// Usually pushes and pops to the undo stack are only
|
|
|
|
// valid inside a startCmd() - endCmd(). Exceptions
|
fix various typos
* Found via `codespell -q 3 -S ./share/locale,./thirdparty -L ba,cann,clas,dur,foto,iff,nd,ois,ot,pres,possibile,snaped,strack,tage,te,uint,thru,valu`
* Some revisions made per feedback given during review.
* Follow-up typos for review
* Add revisions per feedback
2019-12-17 21:06:10 +01:00
|
|
|
// occurred during score loading.
|
2018-11-08 15:45:14 +01:00
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2018-11-29 13:35:34 +01:00
|
|
|
int ScoreLoad::_loading = 0;
|
2013-05-13 18:49:17 +02:00
|
|
|
}
|