MuseScore/libmscore/score.cpp

4835 lines
166 KiB
C++
Raw Normal View History

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"
#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"
#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"
#include "rehearsalmark.h"
#include "breath.h"
#include "instrchange.h"
#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.
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()
{
_first = 0;
_last = 0;
_size = 0;
};
//---------------------------------------------------------
// push_back
//---------------------------------------------------------
void MeasureBaseList::push_back(MeasureBase* e)
{
++_size;
if (_last) {
_last->setNext(e);
e->setPrev(_last);
e->setNext(0);
}
else {
_first = e;
e->setPrev(0);
e->setNext(0);
}
_last = e;
}
//---------------------------------------------------------
// push_front
//---------------------------------------------------------
void MeasureBaseList::push_front(MeasureBase* e)
{
++_size;
if (_first) {
_first->setPrev(e);
e->setNext(_first);
e->setPrev(0);
}
else {
_last = e;
e->setPrev(0);
e->setNext(0);
}
_first = e;
}
//---------------------------------------------------------
// add
// insert e before e->next()
//---------------------------------------------------------
void MeasureBaseList::add(MeasureBase* e)
{
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);
}
//---------------------------------------------------------
2015-06-13 17:57:57 +02:00
// remove
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
void MeasureBaseList::remove(MeasureBase* el)
{
--_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();
}
//---------------------------------------------------------
// insert
//---------------------------------------------------------
void MeasureBaseList::insert(MeasureBase* fm, MeasureBase* lm)
{
++_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;
}
//---------------------------------------------------------
// remove
//---------------------------------------------------------
void MeasureBaseList::remove(MeasureBase* fm, MeasureBase* lm)
{
--_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;
}
//---------------------------------------------------------
// change
//---------------------------------------------------------
void MeasureBaseList::change(MeasureBase* ob, MeasureBase* nb)
{
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;
2017-01-18 14:16:33 +01:00
if (nb->type() == ElementType::HBOX || nb->type() == ElementType::VBOX
|| nb->type() == ElementType::TBOX || nb->type() == ElementType::FBOX)
2012-05-26 14:26:10 +02:00
nb->setSystem(ob->system());
foreach(Element* e, nb->el())
2012-05-26 14:26:10 +02:00
e->setParent(nb);
}
//---------------------------------------------------------
// Score
//---------------------------------------------------------
Score::Score()
: ScoreElement(this), _selection(this), _selectionFilter(this)
{
Score::validScores.insert(this);
2016-03-10 10:41:31 +01:00
_masterScore = 0;
2016-03-11 12:18:46 +01:00
Layer l;
l.name = "default";
l.tags = 1;
_layer.append(l);
_layerTags[0] = "default";
_scoreFont = ScoreFont::fontFactory("emmentaler");
_fileDivision = MScore::division;
2017-01-05 11:23:47 +01:00
_style = MScore::defaultStyle();
// accInfo = tr("No selection"); // ??
accInfo = "No selection";
}
Score::Score(MasterScore* parent, bool forcePartStyle /* = true */)
2016-03-10 10:41:31 +01:00
: Score{}
2012-05-26 14:26:10 +02:00
{
Score::validScores.insert(this);
2016-03-10 10:41:31 +01:00
_masterScore = parent;
if (MScore::defaultStyleForParts())
_style = *MScore::defaultStyleForParts();
else {
// inherit most style settings from parent
_style = parent->style();
2018-03-27 15:36:00 +02:00
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
}
2017-01-05 11:23:47 +01:00
Score::Score(MasterScore* parent, const MStyle& s)
2016-03-10 10:41:31 +01:00
: Score{parent}
{
2019-03-13 16:51:17 +01:00
Score::validScores.insert(this);
2017-01-05 11:23:47 +01:00
_style = s;
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// ~Score
//---------------------------------------------------------
Score::~Score()
{
Score::validScores.erase(this);
2012-05-26 14:26:10 +02:00
foreach(MuseScoreView* v, viewer)
v->removeScore();
2013-10-05 14:03:34 +02:00
// deselectAll();
qDeleteAll(_systems); // systems are layout-only objects so we delete
// them prior to measures.
2012-05-26 14:26:10 +02:00
for (MeasureBase* m = _measures.first(); m;) {
MeasureBase* nm = m->next();
2019-03-19 14:35:10 +01:00
if (m->isMeasure() && toMeasure(m)->mmRest())
delete toMeasure(m)->mmRest();
2012-05-26 14:26:10 +02:00
delete m;
m = nm;
}
2019-03-19 11:26:12 +01:00
for (auto it = _spanner.cbegin(); it != _spanner.cend(); ++it)
delete it->second;
_spanner.clear();
2016-02-04 11:27:47 +01:00
qDeleteAll(_parts);
qDeleteAll(_staves);
// qDeleteAll(_pages); // TODO: check
2016-03-30 22:33:04 +02:00
_masterScore = 0;
2019-03-19 11:25:33 +01:00
imageStore.clearUnused();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// Score::clone
// To create excerpt clone to show when changing PageSettings
// Use MasterScore::clone() instead
//---------------------------------------------------------
Score* Score::clone()
{
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
XmlWriter xml(this, &buffer);
xml.header();
xml.stag("museScore version=\"" MSC_VERSION "\"");
write(xml, false);
xml.etag();
buffer.close();
XmlReader r(buffer.buffer());
MasterScore* score = new MasterScore(style());
score->read1(r, true);
score->addLayoutFlags(LayoutFlag::FIX_PITCH_VELO);
score->doLayout();
return score;
}
//---------------------------------------------------------
// Score::onElementDestruction
// Ensure correct state of the score after destruction
// of the element (e.g. remove invalid pointers etc.).
//---------------------------------------------------------
void Score::onElementDestruction(Element* e)
{
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);
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// addMeasure
//---------------------------------------------------------
void Score::addMeasure(MeasureBase* m, MeasureBase* pos)
{
m->setNext(pos);
_measures.add(m);
}
//---------------------------------------------------------
// 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()
{
Fraction tick = Fraction(0,1);
2012-05-26 14:26:10 +02:00
Measure* fm = firstMeasure();
if (fm == 0)
return;
2014-05-03 11:33:47 +02:00
for (Staff* staff : _staves)
staff->clearTimeSig();
2014-11-27 13:04:03 +01:00
2016-03-10 10:41:31 +01:00
if (isMaster()) {
2012-05-26 14:26:10 +02:00
tempomap()->clear();
2016-03-10 10:41:31 +01:00
sigmap()->clear();
sigmap()->add(0, SigEvent(fm->ticks(), fm->timesig(), 0));
2012-05-26 14:26:10 +02:00
}
for (MeasureBase* mb = first(); mb; mb = mb->next()) {
2017-01-18 14:16:33 +01:00
if (mb->type() != ElementType::MEASURE) {
2012-05-26 14:26:10 +02:00
mb->setTick(tick);
continue;
}
Measure* m = toMeasure(mb);
Fraction mtick = m->tick();
Fraction diff = tick - mtick;
Fraction measureTicks = m->ticks();
2012-05-26 14:26:10 +02:00
m->moveTicks(diff);
2013-09-28 12:05:48 +02:00
if (m->mmRest())
m->mmRest()->moveTicks(diff);
2012-05-26 14:26:10 +02:00
rebuildTempoAndTimeSigMaps(m);
tick += measureTicks;
}
// Now done in getNextMeasure(), do we keep?
if (tempomap()->empty())
tempomap()->setTempo(0, _defaultTempo);
}
//---------------------------------------------------------
// fixTicks
/// updates tempomap and time sig map for a measure
//---------------------------------------------------------
void Score::rebuildTempoAndTimeSigMaps(Measure* measure)
{
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());
2016-01-04 14:48:58 +01:00
}
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());
2012-05-26 14:26:10 +02:00
}
2016-01-04 14:48:58 +01:00
}
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());
2012-05-26 14:26:10 +02:00
}
}
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);
2012-05-26 14:26:10 +02:00
}
2016-01-04 14:48:58 +01:00
}
}
2012-05-26 14:26:10 +02:00
// 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
2016-01-04 14:48:58 +01:00
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
2014-11-27 13:04:03 +01:00
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
}
}
2012-11-08 12:59:30 +01:00
//---------------------------------------------------------
// validSegment
//---------------------------------------------------------
static bool validSegment(Segment* s, int startTrack, int endTrack)
{
for (int track = startTrack; track < endTrack; ++track) {
if (s->element(track))
return true;
}
return false;
}
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
2012-05-26 14:26:10 +02:00
{
Measure* m = searchMeasure(p);
if (m == 0)
return 0;
System* s = m->system();
qreal y = p.y() - s->canvasPos().y();
int i = 0;
for (; i < nstaves();) {
SysStaff* stff = s->staff(i);
if (!stff->show() || !staff(i)->show()) {
2012-05-26 14:26:10 +02:00
++i;
continue;
}
int ni = i;
for (;;) {
++ni;
if (ni == nstaves() || (s->staff(ni)->show() && staff(ni)->show()))
2012-05-26 14:26:10 +02:00
break;
}
qreal sy2;
if (ni != nstaves()) {
SysStaff* nstaff = s->staff(ni);
qreal s1y2 = stff->bbox().y() + stff->bbox().height();
2012-05-26 14:26:10 +02:00
sy2 = s1y2 + (nstaff->bbox().y() - s1y2)/2;
}
else
sy2 = s->page()->height() - s->pos().y(); // s->height();
if (y > sy2) {
i = ni;
continue;
}
break;
}
// search for segment + offset
QPointF pppp = p - m->canvasPos();
2012-11-08 12:59:30 +01:00
int strack = i * VOICES;
if (!staff(i))
2015-12-11 14:30:54 +01:00
return 0;
// int etrack = staff(i)->part()->nstaves() * VOICES + strack;
int etrack = VOICES + strack;
2012-05-26 14:26:10 +02:00
SysStaff* sstaff = m->system()->staff(i);
2017-03-08 13:12:26 +01:00
SegmentType st = SegmentType::ChordRest;
2012-11-08 12:59:30 +01:00
for (Segment* segment = m->first(st); segment; segment = segment->next(st)) {
if (!validSegment(segment, strack, etrack))
2012-05-26 14:26:10 +02:00
continue;
2012-11-08 12:59:30 +01:00
Segment* ns = segment->next(st);
for (; ns; ns = ns->next(st)) {
if (validSegment(ns, strack, etrack))
2012-05-26 14:26:10 +02:00
break;
}
if (!ns || (pppp.x() < (segment->x() + (ns->x() - segment->x())/2.0))) {
*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));
2012-05-26 14:26:10 +02:00
}
if (offset)
*offset = pppp - QPointF(segment->x(), sstaff->bbox().y());
if (seg)
*seg = segment;
return m;
}
}
return 0;
}
//---------------------------------------------------------
// dragPosition
// on input:
// p - canvas relative drag position
// rst - current staff index
// seg - current segment
// on output:
// rst - new staff index for drag position
// seg - new segment for drag position
//---------------------------------------------------------
void Score::dragPosition(const QPointF& p, int* rst, Segment** seg) const
{
Measure* m = searchMeasure(p);
if (m == 0)
return;
System* s = m->system();
qreal y = p.y() - s->canvasPos().y();
int i;
for (i = 0; i < nstaves();) {
SysStaff* stff = s->staff(i);
if (!stff->show() || !staff(i)->show()) {
++i;
continue;
}
int ni = i;
for (;;) {
++ni;
if (ni == nstaves() || (s->staff(ni)->show() && staff(ni)->show()))
break;
}
qreal sy2;
if (ni != nstaves()) {
SysStaff* nstaff = s->staff(ni);
qreal s1y2 = stff->bbox().y() + stff->bbox().height();
if (i == *rst)
sy2 = s1y2 + (nstaff->bbox().y() - s1y2);
else if (ni == *rst)
sy2 = s1y2;
else
sy2 = s1y2 + (nstaff->bbox().y() - s1y2) * .5;
}
else
sy2 = s->page()->height() - s->pos().y();
if (y > sy2) {
i = ni;
continue;
}
break;
}
// search for segment + offset
QPointF pppp = p - m->canvasPos();
int strack = i * VOICES;
if (!staff(i))
return;
int etrack = staff(i)->part()->nstaves() * VOICES + strack;
2017-03-08 13:12:26 +01:00
SegmentType st = SegmentType::ChordRest;
for (Segment* segment = m->first(st); segment; segment = segment->next(st)) {
if (!validSegment(segment, strack, etrack))
continue;
Segment* ns = segment->next(st);
for (; ns; ns = ns->next(st)) {
if (validSegment(ns, strack, etrack))
break;
}
if (!ns) {
*rst = i;
*seg = segment;
return;
}
if (*seg == segment) {
if (pppp.x() < (segment->x() + (ns->x() - segment->x()))) {
*rst = i;
*seg = segment;
return;
}
}
else if (*seg == ns) {
if (pppp.x() <= segment->x()) {
*rst = i;
*seg = segment;
return;
}
}
else {
if (pppp.x() < (segment->x() + (ns->x() - segment->x())/2.0)) {
*rst = i;
*seg = segment;
return;
}
}
}
return;
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// setShowInvisible
//---------------------------------------------------------
void Score::setShowInvisible(bool v)
{
_showInvisible = v;
2016-03-02 13:20:19 +01:00
setUpdateAll();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// setShowUnprintable
//---------------------------------------------------------
void Score::setShowUnprintable(bool v)
{
_showUnprintable = v;
2016-03-02 13:20:19 +01:00
setUpdateAll();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// setShowFrames
//---------------------------------------------------------
void Score::setShowFrames(bool v)
{
_showFrames = v;
2016-03-02 13:20:19 +01:00
setUpdateAll();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// setShowPageborders
//---------------------------------------------------------
void Score::setShowPageborders(bool v)
{
_showPageborders = v;
2016-03-02 13:20:19 +01:00
setUpdateAll();
2012-05-26 14:26:10 +02:00
}
2016-08-06 11:36:51 +02:00
//---------------------------------------------------------
// setMarkIrregularMeasures
//---------------------------------------------------------
void Score::setMarkIrregularMeasures(bool v)
{
_markIrregularMeasures = v;
setUpdateAll();
}
2018-08-03 01:15:56 +02:00
//---------------------------------------------------------
// readOnly
//---------------------------------------------------------
bool Score::readOnly() const
{
return _masterScore->readOnly();
}
2014-02-25 14:14:59 +01:00
//---------------------------------------------------------
// dirty
//---------------------------------------------------------
bool Score::dirty() const
{
2016-03-10 10:41:31 +01: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
{
return ScoreContentState(this, undoStack()->state());
2018-07-23 19:50:53 +02:00
}
//---------------------------------------------------------
// playlistDirty
//---------------------------------------------------------
bool Score::playlistDirty() const
{
return masterScore()->playlistDirty();
}
//---------------------------------------------------------
// setPlaylistDirty
//---------------------------------------------------------
void Score::setPlaylistDirty()
{
masterScore()->setPlaylistDirty();
}
//---------------------------------------------------------
// setPlaylistDirty
//---------------------------------------------------------
void MasterScore::setPlaylistDirty()
{
_playlistDirty = true;
_repeatList->setScoreChanged();
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// spell
//---------------------------------------------------------
void Score::spell()
{
for (int i = 0; i < nstaves(); ++i) {
2016-02-06 22:03:43 +01:00
std::vector<Note*> notes;
2017-03-08 13:12:26 +01:00
for (Segment* s = firstSegment(SegmentType::All); s; s = s->next1()) {
2012-05-26 14:26:10 +02:00
int strack = i * VOICES;
int etrack = strack + VOICES;
for (int track = strack; track < etrack; ++track) {
Element* e = s->element(track);
2017-01-18 14:16:33 +01:00
if (e && e->type() == ElementType::CHORD)
2016-02-06 22:03:43 +01:00
notes.insert(notes.end(),
2016-06-03 10:17:06 +02:00
toChord(e)->notes().begin(),
toChord(e)->notes().end());
2012-05-26 14:26:10 +02:00
}
}
spellNotelist(notes);
}
}
void Score::spell(int startStaff, int endStaff, Segment* startSegment, Segment* endSegment)
{
for (int i = startStaff; i < endStaff; ++i) {
2016-02-06 22:03:43 +01:00
std::vector<Note*> notes;
2012-05-26 14:26:10 +02:00
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);
2017-01-18 14:16:33 +01:00
if (e && e->type() == ElementType::CHORD)
2016-02-06 22:03:43 +01:00
notes.insert(notes.end(),
2016-06-03 10:17:06 +02:00
toChord(e)->notes().begin(),
toChord(e)->notes().end());
2012-05-26 14:26:10 +02:00
}
}
spellNotelist(notes);
}
}
//---------------------------------------------------------
// prevNote
//---------------------------------------------------------
Note* prevNote(Note* n)
{
Chord* chord = n->chord();
Segment* seg = chord->segment();
2016-02-06 22:03:43 +01:00
const std::vector<Note*> nl = chord->notes();
auto i = std::find(nl.begin(), nl.end(), n);
2016-02-09 13:51:19 +01:00
if (i != nl.begin())
2016-02-06 22:03:43 +01:00
return *(i-1);
2012-05-26 14:26:10 +02:00
int staff = n->staffIdx();
int startTrack = staff * VOICES + n->voice() - 1;
int endTrack = 0;
while (seg) {
2017-03-08 13:12:26 +01:00
if (seg->segmentType() == SegmentType::ChordRest) {
2012-05-26 14:26:10 +02:00
for (int track = startTrack; track >= endTrack; --track) {
Element* e = seg->element(track);
2017-01-18 14:16:33 +01:00
if (e && e->type() == ElementType::CHORD)
2016-06-03 10:17:06 +02:00
return toChord(e)->upNote();
2012-05-26 14:26:10 +02:00
}
}
seg = seg->prev1();
startTrack = staff * VOICES + VOICES - 1;
}
return n;
}
//---------------------------------------------------------
// nextNote
//---------------------------------------------------------
2016-02-09 09:20:54 +01:00
static Note* nextNote(Note* n)
2012-05-26 14:26:10 +02:00
{
Chord* chord = n->chord();
2016-02-06 22:03:43 +01:00
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;
}
2012-05-26 14:26:10 +02:00
Segment* seg = chord->segment();
int staff = n->staffIdx();
int startTrack = staff * VOICES + n->voice() + 1;
int endTrack = staff * VOICES + VOICES;
while (seg) {
2017-03-08 13:12:26 +01:00
if (seg->segmentType() == SegmentType::ChordRest) {
2012-05-26 14:26:10 +02:00
for (int track = startTrack; track < endTrack; ++track) {
Element* e = seg->element(track);
2017-01-18 14:16:33 +01:00
if (e && e->type() == ElementType::CHORD) {
2012-05-26 14:26:10 +02:00
return ((Chord*)e)->downNote();
}
}
}
seg = seg->next1();
startTrack = staff * VOICES;
}
return n;
}
//---------------------------------------------------------
// spell
//---------------------------------------------------------
void Score::spell(Note* note)
{
2016-02-06 22:03:43 +01:00
std::vector<Note*> notes;
2012-05-26 14:26:10 +02:00
2016-02-06 22:03:43 +01:00
notes.push_back(note);
2012-05-26 14:26:10 +02:00
Note* nn = nextNote(note);
2016-02-06 22:03:43 +01:00
notes.push_back(nn);
2012-05-26 14:26:10 +02:00
nn = nextNote(nn);
2016-02-06 22:03:43 +01:00
notes.push_back(nn);
2012-05-26 14:26:10 +02:00
nn = nextNote(nn);
2016-02-06 22:03:43 +01:00
notes.push_back(nn);
2012-05-26 14:26:10 +02:00
nn = prevNote(note);
2016-02-06 22:03:43 +01:00
notes.insert(notes.begin(), nn);
2012-05-26 14:26:10 +02:00
nn = prevNote(nn);
2016-02-06 22:03:43 +01:00
notes.insert(notes.begin(), nn);
2012-05-26 14:26:10 +02:00
nn = prevNote(nn);
2016-02-06 22:03:43 +01:00
notes.insert(notes.begin(), nn);
2012-05-26 14:26:10 +02:00
2013-05-13 18:49:17 +02:00
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)
{
_parts.append(p);
}
//---------------------------------------------------------
// searchPage
// p is in canvas coordinates
//---------------------------------------------------------
Page* Score::searchPage(const QPointF& p) const
{
2015-12-11 14:30:54 +01:00
for (Page* page : pages()) {
QRectF r = page->bbox().translated(page->pos());
if (r.contains(p))
2012-05-26 14:26:10 +02:00
return page;
}
return 0;
}
//---------------------------------------------------------
// searchSystem
// return list of systems as there may be more than
// one system in a row
// p is in canvas coordinates
//---------------------------------------------------------
QList<System*> Score::searchSystem(const QPointF& pos) const
{
QList<System*> systems;
Page* page = searchPage(pos);
if (page == 0)
return systems;
qreal y = pos.y() - page->pos().y(); // transform to page relative
2016-01-04 14:48:58 +01:00
const QList<System*>* sl = &page->systems();
2012-05-26 14:26:10 +02:00
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 {
2012-05-26 14:26:10 +02:00
qreal sy2 = s->y() + s->bbox().height();
y2 = sy2 + (ns->y() - sy2) * .5;
}
if (y < y2) {
systems.append(s);
for (int iii = i+1; ii < n; ++iii) {
if (sl->at(iii)->y() != s->y())
2012-05-26 14:26:10 +02:00
break;
systems.append(sl->at(iii));
2012-05-26 14:26:10 +02:00
}
return systems;
}
}
return systems;
}
//---------------------------------------------------------
// searchMeasure
// p is in canvas coordinates
//---------------------------------------------------------
Measure* Score::searchMeasure(const QPointF& p) const
{
QList<System*> systems = searchSystem(p);
2017-05-02 14:17:31 +02:00
for (System* system : systems) {
2012-05-26 14:26:10 +02:00
qreal x = p.x() - system->canvasPos().x();
2017-05-02 14:17:31 +02:00
for (MeasureBase* mb : system->measures()) {
if (mb->isMeasure() && (x < (mb->x() + mb->bbox().width())))
2016-06-03 10:17:06 +02:00
return toMeasure(mb);
2012-05-26 14:26:10 +02:00
}
}
return 0;
}
//---------------------------------------------------------
// 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)
{
if (s == 0)
return 0;
2017-03-08 13:12:26 +01:00
Q_ASSERT(s->segmentType() == SegmentType::ChordRest);
2012-05-26 14:26:10 +02:00
// Segment* s1 = s;
ChordRest* cr1 = nullptr;
2017-03-08 13:12:26 +01:00
for (Segment* s1 = s; s1; s1 = s1->prev(SegmentType::ChordRest)) {
2016-06-03 10:17:06 +02:00
cr1 = toChordRest(s1->element(track + voice));
2012-05-26 14:26:10 +02:00
if (cr1)
break;
}
Fraction nextTick = (cr1 == nullptr) ? s->measure()->tick() : cr1->tick() + cr1->actualTicks();
2012-05-26 14:26:10 +02:00
2017-03-08 13:12:26 +01:00
static const SegmentType st { SegmentType::ChordRest };
2012-05-26 14:26:10 +02:00
while (s) {
if (s->element(track + voice))
break;
if (voice && s->tick() == nextTick)
return s;
#if 0
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)) {
2016-06-03 10:17:06 +02:00
ChordRest* cr = toChordRest(s1->element(track + voice));
2012-05-26 14:26:10 +02:00
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;
}
#endif
s = s->next(st);
}
return s;
}
//---------------------------------------------------------
// getPosition
// return true if valid position found
//---------------------------------------------------------
bool Score::getPosition(Position* pos, const QPointF& p, int voice) const
{
2012-08-07 12:44:19 +02:00
Measure* measure = searchMeasure(p);
if (measure == 0)
2012-05-26 14:26:10 +02:00
return false;
pos->fret = FRET_NONE;
2012-05-26 14:26:10 +02:00
//
// search staff
//
pos->staffIdx = 0;
SysStaff* sstaff = 0;
2012-08-07 12:44:19 +02:00
System* system = measure->system();
2012-05-26 14:26:10 +02:00
qreal y = p.y() - system->pagePos().y();
for (; pos->staffIdx < nstaves(); ++pos->staffIdx) {
2014-07-31 14:12:34 +02:00
Staff* st = staff(pos->staffIdx);
if (!st->part()->show())
2014-07-31 14:12:34 +02:00
continue;
2012-05-26 14:26:10 +02:00
qreal sy2;
SysStaff* ss = system->staff(pos->staffIdx);
if (!ss->show())
continue;
2014-07-31 14:12:34 +02:00
SysStaff* nstaff = 0;
// find next visible staff
for (int i = pos->staffIdx + 1; i < nstaves(); ++i) {
Staff* sti = staff(i);
if (!sti->part()->show())
2014-07-31 14:12:34 +02:00
continue;
nstaff = system->staff(i);
if (!nstaff->show()) {
nstaff = 0;
continue;
}
2014-07-31 14:12:34 +02:00
break;
}
if (nstaff) {
qreal s1y2 = ss->bbox().bottom();
sy2 = system->page()->canvasPos().y() + s1y2 + (nstaff->bbox().y() - s1y2) * .5;
2012-05-26 14:26:10 +02:00
}
else
sy2 = system->page()->canvasPos().y() + system->page()->height() - system->pagePos().y(); // system->height();
2012-05-26 14:26:10 +02:00
if (y < sy2) {
sstaff = ss;
break;
}
}
if (sstaff == 0)
return false;
//
// search segment
//
2012-08-07 12:44:19 +02:00
QPointF pppp(p - measure->canvasPos());
2012-05-26 14:26:10 +02:00
qreal x = pppp.x();
Segment* segment = 0;
pos->segment = 0;
// int track = pos->staffIdx * VOICES + voice;
int track = pos->staffIdx * VOICES;
2017-03-08 13:12:26 +01:00
for (segment = measure->first(SegmentType::ChordRest); segment;) {
2012-05-26 14:26:10 +02:00
segment = getNextValidInputSegment(segment, track, voice);
if (segment == 0)
break;
2017-03-08 13:12:26 +01:00
Segment* ns = getNextValidInputSegment(segment->next(SegmentType::ChordRest), track, voice);
2012-05-26 14:26:10 +02:00
qreal x1 = segment->x();
qreal x2;
qreal d;
if (ns) {
x2 = ns->x();
d = x2 - x1;
}
else {
2012-08-07 12:44:19 +02:00
x2 = measure->bbox().width();
2012-05-26 14:26:10 +02:00
d = (x2 - x1) * 2.0;
x = x1;
pos->segment = segment;
break;
}
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)
//
Staff* s = staff(pos->staffIdx);
qreal mag = s->mag(segment->tick());
Fraction tick = segment->tick();
// in TABs, step from one string to another; in other staves, step on and between lines
2016-12-13 13:16:17 +01:00
qreal lineDist = s->staffType(tick)->lineDistance().val() * (s->isTabStaff(measure->tick()) ? 1 : .5) * mag * spatium();
2012-05-26 14:26:10 +02:00
pos->line = lrint((pppp.y() - sstaff->bbox().y()) / lineDist);
2016-12-13 13:16:17 +01:00
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;
if (pos->line < 0)
pos->line = 0;
2016-12-13 13:16:17 +01:00
else if (pos->line >= s->lines(tick))
pos->line = s->lines(tick) - 1;
2012-05-26 14:26:10 +02:00
}
else {
2012-08-07 16:05:37 +02:00
int minLine = absStep(0);
2012-08-07 12:44:19 +02:00
ClefType clef = s->clef(pos->segment->tick());
2012-08-07 16:05:37 +02:00
minLine = relStep(minLine, clef);
int maxLine = absStep(127);
maxLine = relStep(maxLine, clef);
2012-05-26 14:26:10 +02:00
if (pos->line > minLine || pos->line < maxLine)
return false;
}
y = sstaff->y() + pos->line * lineDist;
2012-08-07 12:44:19 +02:00
pos->pos = QPointF(x, y) + measure->canvasPos();
2012-05-26 14:26:10 +02:00
return true;
}
//---------------------------------------------------------
// checkHasMeasures
//---------------------------------------------------------
bool Score::checkHasMeasures() const
{
Page* page = pages().isEmpty() ? 0 : pages().front();
const QList<System*>* sl = page ? &page->systems() : 0;
2012-05-26 14:26:10 +02:00
if (sl == 0 || sl->empty() || sl->front()->measures().empty()) {
2013-07-25 17:22:49 +02:00
qDebug("first create measure, then repeat operation");
2012-05-26 14:26:10 +02:00
return false;
}
return true;
}
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)
{
2016-02-04 11:27:47 +01:00
for (System* system : systems())
system->moveBracket(staffIdx, srcCol, dstCol);
2012-05-26 14:26:10 +02:00
}
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)
{
qreal* val = (qreal*)data;
e->spatiumChanged(val[0], val[1]);
}
//---------------------------------------------------------
// spatiumChanged
//---------------------------------------------------------
void Score::spatiumChanged(qreal oldValue, qreal newValue)
{
qreal data[2];
data[0] = oldValue;
data[1] = newValue;
scanElements(data, spatiumHasChanged, true);
for (Staff* staff : _staves)
2012-05-26 14:26:10 +02:00
staff->spatiumChanged(oldValue, newValue);
2015-11-16 14:24:47 +01:00
_noteHeadWidth = _scoreFont->width(SymId::noteheadBlack, newValue / SPATIUM20);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// updateStyle
//---------------------------------------------------------
static void updateStyle(void*, Element* e)
{
bool v = e->generated();
e->styleChanged();
e->setGenerated(v);
}
//---------------------------------------------------------
// styleChanged
// must be called after every style change
//---------------------------------------------------------
void Score::styleChanged()
{
scanElements(0, updateStyle);
if (headerText())
headerText()->styleChanged();
if (footerText())
footerText()->styleChanged();
setLayoutAll();
}
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
//---------------------------------------------------------
Measure* Score::getCreateMeasure(const Fraction& tick)
2012-05-26 14:26:10 +02:00
{
Measure* last = lastMeasure();
if (last == 0 || ((last->tick() + last->ticks()) <= tick)) {
Fraction lastTick = last ? (last->tick()+last->ticks()) : Fraction(0,1);
2012-05-26 14:26:10 +02:00
while (tick >= lastTick) {
Measure* m = new Measure(this);
2016-03-10 10:41:31 +01:00
Fraction ts = sigmap()->timesig(lastTick).timesig();
2012-05-26 14:26:10 +02:00
m->setTick(lastTick);
m->setTimesig(ts);
m->setTicks(ts);
2017-12-20 16:49:30 +01:00
measures()->add(toMeasureBase(m));
lastTick += Fraction::fromTicks(ts.ticks());
2012-05-26 14:26:10 +02:00
}
}
return tick2measure(tick);
}
//---------------------------------------------------------
// 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)
{
2016-02-04 11:27:47 +01: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
2017-01-18 14:16:33 +01:00
ElementType et = element->type();
if (et == ElementType::MEASURE
|| (et == ElementType::HBOX && !(parent && parent->isVBox()))
2017-01-18 14:16:33 +01:00
|| et == ElementType::VBOX
|| et == ElementType::TBOX
|| et == ElementType::FBOX
2012-05-26 14:26:10 +02:00
) {
2017-12-20 16:49:30 +01:00
measures()->add(toMeasureBase(element));
2019-10-24 15:49:23 +02:00
element->triggerLayout();
2012-05-26 14:26:10 +02:00
return;
}
2016-02-04 11:27:47 +01:00
if (parent)
parent->add(element);
2012-05-26 14:26:10 +02:00
switch (et) {
2017-01-18 14:16:33 +01:00
case ElementType::BEAM:
2014-07-03 15:02:36 +02:00
{
2016-06-03 10:17:06 +02:00
Beam* b = toBeam(element);
2014-07-03 15:02:36 +02:00
int n = b->elements().size();
for (int i = 0; i < n; ++i)
b->elements().at(i)->setBeam(b);
}
break;
2017-01-18 14:16:33 +01:00
case ElementType::SLUR:
addLayoutFlags(LayoutFlag::PLAY_EVENTS);
2013-06-20 18:48:28 +02:00
// fall through
2017-01-18 14:16:33 +01:00
case ElementType::VOLTA:
case ElementType::TRILL:
case ElementType::VIBRATO:
2017-01-18 14:16:33 +01:00
case ElementType::PEDAL:
case ElementType::TEXTLINE:
case ElementType::HAIRPIN:
2017-11-27 09:56:41 +01:00
case ElementType::LET_RING:
case ElementType::PALM_MUTE:
2012-05-26 14:26:10 +02:00
{
2017-12-20 16:49:30 +01:00
Spanner* spanner = toSpanner(element);
2017-01-18 14:16:33 +01:00
if (et == ElementType::TEXTLINE && spanner->anchor() == Spanner::Anchor::NOTE)
2013-07-04 13:40:25 +02:00
break;
2014-07-09 18:05:58 +02:00
addSpanner(spanner);
for (SpannerSegment* ss : spanner->spannerSegments()) {
2012-05-26 14:26:10 +02:00
if (ss->system())
ss->system()->add(ss);
}
}
break;
2017-01-18 14:16:33 +01:00
case ElementType::OTTAVA:
2012-05-26 14:26:10 +02:00
{
2016-06-03 10:17:06 +02:00
Ottava* o = toOttava(element);
2014-07-03 15:02:36 +02:00
addSpanner(o);
2012-05-26 14:26:10 +02:00
foreach(SpannerSegment* ss, o->spannerSegments()) {
if (ss->system())
ss->system()->add(ss);
}
2016-03-18 09:29:16 +01:00
cmdState().layoutFlags |= LayoutFlag::FIX_PITCH_VELO;
2014-07-15 12:49:51 +02:00
o->staff()->updateOttava();
setPlaylistDirty();
2012-05-26 14:26:10 +02:00
}
break;
2017-01-18 14:16:33 +01:00
case ElementType::DYNAMIC:
2016-03-18 09:29:16 +01:00
cmdState().layoutFlags |= LayoutFlag::FIX_PITCH_VELO;
setPlaylistDirty();
2012-05-26 14:26:10 +02:00
break;
2013-07-05 11:23:52 +02:00
2017-01-18 14:16:33 +01:00
case ElementType::TEMPO_TEXT:
fixTicks(); // rebuilds tempomap
2012-05-26 14:26:10 +02:00
break;
2013-07-05 11:23:52 +02:00
2017-01-18 14:16:33 +01:00
case ElementType::INSTRUMENT_CHANGE: {
2016-06-03 10:17:06 +02:00
InstrumentChange* ic = toInstrumentChange(element);
2018-07-27 21:55:07 +02:00
ic->part()->setInstrument(ic->instrument(), ic->segment()->tick());
#if 0
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
2016-03-11 12:18:46 +01:00
masterScore()->rebuildMidiMapping();
2016-03-18 09:29:16 +01:00
cmdState()._instrumentsChanged = true;
}
2012-05-26 14:26:10 +02:00
break;
2017-01-18 14:16:33 +01:00
case ElementType::CHORD:
2015-01-30 17:03:51 +01:00
setPlaylistDirty();
// create playlist does not work here bc. tremolos may not be complete
2016-06-03 10:17:06 +02:00
// createPlayEvents(toChord(element));
2012-11-21 15:57:35 +01:00
break;
2017-01-18 14:16:33 +01:00
case ElementType::NOTE:
case ElementType::TREMOLO:
case ElementType::ARTICULATION:
case ElementType::ARPEGGIO:
{
2016-02-04 11:27:47 +01:00
Element* cr = parent;
if (cr->isChord())
createPlayEvents(toChord(cr));
}
2016-06-03 10:17:06 +02:00
break;
2012-05-26 14:26:10 +02:00
default:
break;
}
2019-10-24 15:49:23 +02:00
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)
{
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
2012-05-26 14:26:10 +02:00
// special for MEASURE, HBOX, VBOX
// their parent is not static
2017-01-18 14:16:33 +01:00
ElementType et = element->type();
2013-06-05 15:47:34 +02:00
2017-01-18 14:16:33 +01:00
if (et == ElementType::MEASURE
|| (et == ElementType::HBOX && !parent->isVBox())
|| et == ElementType::VBOX
|| et == ElementType::TBOX
|| et == ElementType::FBOX
2012-05-26 14:26:10 +02:00
) {
2017-02-15 13:58:54 +01:00
MeasureBase* mb = toMeasureBase(element);
measures()->remove(mb);
System* system = mb->system();
if (!system) { // vertical boxes are not shown in continuous view so no system
Q_ASSERT(lineMode() && (element->isVBox() || element->isTBox()));
return;
}
2017-02-15 13:58:54 +01:00
Page* page = system->page();
if (element->isBox() && system->measures().size() == 1) {
2017-02-15 13:58:54 +01:00
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++;
}
}
2017-02-15 13:58:54 +01:00
}
// setLayout(mb->tick());
2012-05-26 14:26:10 +02:00
return;
}
2017-03-14 17:00:38 +01:00
if (et == ElementType::BEAM) { // beam parent does not survive layout
2012-05-26 14:26:10 +02:00
element->setParent(0);
2017-03-14 17:00:38 +01:00
parent = 0;
}
2012-05-26 14:26:10 +02:00
if (parent)
parent->remove(element);
2016-02-09 13:51:19 +01:00
switch (et) {
2017-01-18 14:16:33 +01:00
case ElementType::BEAM:
2016-06-03 10:17:06 +02:00
for (ChordRest* cr : toBeam(element)->elements())
2014-07-03 15:02:36 +02:00
cr->setBeam(0);
break;
2017-01-18 14:16:33 +01:00
case ElementType::SLUR:
addLayoutFlags(LayoutFlag::PLAY_EVENTS);
2013-06-20 18:48:28 +02:00
// fall through
2012-05-26 14:26:10 +02:00
2017-01-18 14:16:33 +01:00
case ElementType::VOLTA:
case ElementType::TRILL:
case ElementType::VIBRATO:
2017-01-18 14:16:33 +01:00
case ElementType::PEDAL:
2017-11-27 09:56:41 +01:00
case ElementType::LET_RING:
case ElementType::PALM_MUTE:
2017-01-18 14:16:33 +01:00
case ElementType::TEXTLINE:
case ElementType::HAIRPIN:
2012-05-26 14:26:10 +02:00
{
2017-12-20 16:49:30 +01:00
Spanner* spanner = toSpanner(element);
2017-01-18 14:16:33 +01:00
if (et == ElementType::TEXTLINE && spanner->anchor() == Spanner::Anchor::NOTE)
2014-07-09 18:05:58 +02:00
break;
2019-10-24 15:49:23 +02:00
spanner->triggerLayout();
2014-07-03 15:02:36 +02:00
removeSpanner(spanner);
2012-05-26 14:26:10 +02:00
}
break;
2017-01-18 14:16:33 +01:00
case ElementType::OTTAVA:
2012-05-26 14:26:10 +02:00
{
2016-06-03 10:17:06 +02:00
Ottava* o = toOttava(element);
2019-10-24 15:49:23 +02:00
o->triggerLayout();
2014-07-03 15:02:36 +02:00
removeSpanner(o);
2014-07-15 12:49:51 +02:00
o->staff()->updateOttava();
2016-03-18 09:29:16 +01:00
cmdState().layoutFlags |= LayoutFlag::FIX_PITCH_VELO;
setPlaylistDirty();
2012-05-26 14:26:10 +02:00
}
break;
2017-01-18 14:16:33 +01:00
case ElementType::DYNAMIC:
2016-03-18 09:29:16 +01:00
cmdState().layoutFlags |= LayoutFlag::FIX_PITCH_VELO;
setPlaylistDirty();
2012-05-26 14:26:10 +02:00
break;
2017-01-18 14:16:33 +01:00
case ElementType::CHORD:
case ElementType::REST:
2012-05-26 14:26:10 +02:00
{
2016-06-03 10:17:06 +02:00
ChordRest* cr = toChordRest(element);
2012-05-26 14:26:10 +02:00
if (cr->beam())
cr->beam()->remove(cr);
2016-08-17 12:52:35 +02:00
for (Lyrics* lyr : cr->lyrics())
2016-08-24 14:49:34 +02:00
lyr->removeFromScore();
2014-05-21 20:08:37 +02:00
// TODO: check for tuplet?
2012-05-26 14:26:10 +02:00
}
break;
2017-01-18 14:16:33 +01:00
case ElementType::TEMPO_TEXT:
fixTicks(); // rebuilds tempomap
2012-05-26 14:26:10 +02:00
break;
2017-01-18 14:16:33 +01:00
case ElementType::INSTRUMENT_CHANGE: {
2016-06-03 10:17:06 +02:00
InstrumentChange* ic = toInstrumentChange(element);
2018-07-27 21:55:07 +02:00
ic->part()->removeInstrument(ic->segment()->tick());
#if 0
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
2016-03-11 12:18:46 +01:00
masterScore()->rebuildMidiMapping();
2016-03-18 09:29:16 +01:00
cmdState()._instrumentsChanged = true;
}
2012-05-26 14:26:10 +02:00
break;
2012-11-20 20:51:18 +01:00
2017-01-18 14:16:33 +01:00
case ElementType::TREMOLO:
case ElementType::ARTICULATION:
case ElementType::ARPEGGIO:
{
Element* cr = element->parent();
if (cr->isChord())
createPlayEvents(toChord(cr));
}
2016-06-03 10:17:06 +02:00
break;
2012-11-20 20:51:18 +01:00
2012-05-26 14:26:10 +02:00
default:
break;
}
}
//---------------------------------------------------------
// firstMeasure
//---------------------------------------------------------
Measure* Score::firstMeasure() const
{
MeasureBase* mb = _measures.first();
2017-01-18 14:16:33 +01:00
while (mb && mb->type() != ElementType::MEASURE)
2012-05-26 14:26:10 +02:00
mb = mb->next();
2016-06-03 10:17:06 +02:00
return toMeasure(mb);
}
//---------------------------------------------------------
// firstMeasureMM
//---------------------------------------------------------
Measure* Score::firstMeasureMM() const
{
2016-03-02 13:20:19 +01:00
Measure* m = firstMeasure();
2018-03-27 15:36:00 +02:00
if (m && styleB(Sid::createMultiMeasureRests) && m->hasMMRest())
return m->mmRest();
return m;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// firstMM
//---------------------------------------------------------
MeasureBase* Score::firstMM() const
{
MeasureBase* m = _measures.first();
if (m
2017-01-18 14:16:33 +01:00
&& m->type() == ElementType::MEASURE
2018-03-27 15:36:00 +02:00
&& styleB(Sid::createMultiMeasureRests)
2016-06-03 10:17:06 +02:00
&& toMeasure(m)->hasMMRest()) {
return toMeasure(m)->mmRest();
}
return m;
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// measure
//---------------------------------------------------------
MeasureBase* Score::measure(int idx) const
{
MeasureBase* mb = _measures.first();
for (int i = 0; i < idx; ++i) {
mb = mb->next();
if (mb == 0)
return 0;
}
return mb;
}
//---------------------------------------------------------
// crMeasure
// Returns a measure containing chords an rests
// by its index skipping other MeasureBase descendants
//---------------------------------------------------------
Measure* Score::crMeasure(int idx) const
{
int i = -1;
for (MeasureBase* mb = _measures.first(); mb; mb = mb->next()) {
if (mb->isMeasure())
++i;
if (i == idx)
return toMeasure(mb);
}
return nullptr;
}
//---------------------------------------------------------
2012-05-26 14:26:10 +02:00
// lastMeasure
//---------------------------------------------------------
Measure* Score::lastMeasure() const
{
MeasureBase* mb = _measures.last();
2017-01-18 14:16:33 +01:00
while (mb && mb->type() != ElementType::MEASURE)
2012-05-26 14:26:10 +02:00
mb = mb->prev();
2016-06-03 10:17:06 +02:00
return toMeasure(mb);
2012-05-26 14:26:10 +02:00
}
2013-10-06 16:43:43 +02:00
//---------------------------------------------------------
// lastMeasureMM
//---------------------------------------------------------
Measure* Score::lastMeasureMM() const
{
2014-08-17 12:41:44 +02:00
Measure* m = lastMeasure();
2018-03-27 15:36:00 +02:00
if (m && styleB(Sid::createMultiMeasureRests)) {
2016-06-03 10:17:06 +02:00
Measure* m1 = const_cast<Measure*>(toMeasure(m->mmRest1()));
2015-01-28 04:56:28 +01:00
if (m1)
return m1;
}
2014-08-17 12:41:44 +02:00
return m;
2013-10-06 16:43:43 +02:00
}
2017-02-13 17:06:30 +01:00
//---------------------------------------------------------
// endTick
//---------------------------------------------------------
Fraction Score::endTick() const
2017-02-13 17:06:30 +01: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
2012-05-26 14:26:10 +02:00
{
Segment* seg;
2012-05-26 14:26:10 +02:00
Measure* m = firstMeasure();
if (!m)
seg = 0;
else {
seg = m->first();
if (seg && !(seg->segmentType() & segType))
seg = seg->next1(segType);
}
Fixes the following Q_INVOKABLE methods returning a QObject* by turning them into a property: - Measure: -- firstSegment -- lastSegment - MeasureBase: -- nextMeasure -- nextMeasureMM (new) -- prevMeasure -- prevMeasureMM (new) - Score: -- firstMeasure -- firstMeasureMM (new) -- (for firstSegment(), see special cases below) -- lastMeasure -- lastMeasureMM (new) -- lastSegment - Segment: -- next (renamed from `next1`) -- nextInMeasure (renamed from `next`) -- prev (renamed from `prev1`) -- prevInMeasure (renamed from prev) Special cases: - Cursor: The prototype of the `Q_INVOKABLE Ms::Note* Cursor::addNote(int pitch)` was wrong: corrected in `Q_INVOKABLE void Cursor::addNote(int pitch)`. - QmlPlugin: `Q_INVOKABLE Score* QmlPlugin::readScore()` and `Q_INVOKABLE Score* QmlPlugin::newScore()` has been kept, as they are intended to be called from QML; code has been added to ensure the C++ ownership of the returned object. - Score: `Q_INVOKABLE Segment* Score::firstSegment(Segment::Type segType)` is kept (as it needs a parameters), but code is added to ensure C++ ownership of the returned Segment*. - Segment: `Ms::Element* Segment::element(int track)` has been made NOT Q_INVOKABLE; a variant `Q_INVOKABLE Ms::Element* elementAt(int track)` has been added specifically for QML with code to ensure the C++ ownership of the returned Element* (this was the cause for the crash of the Walk plug-in). - FiguredBass: `Q_INVOKABLE Ms::FiguredBassItem* FiguredBass::addItem()` has been removed; plugin interface for FiguredBass needs to be redesigned anyway. The few occurrences in the supplied plug-ins of the methods whose names did change have been updated.
2014-07-06 01:56:30 +02:00
#ifdef SCRIPT_INTERFACE
// if called from QML/JS, tell QML engine not to garbage collect this object
// if (seg)
// QQmlEngine::setObjectOwnership(seg, QQmlEngine::CppOwnership);
Fixes the following Q_INVOKABLE methods returning a QObject* by turning them into a property: - Measure: -- firstSegment -- lastSegment - MeasureBase: -- nextMeasure -- nextMeasureMM (new) -- prevMeasure -- prevMeasureMM (new) - Score: -- firstMeasure -- firstMeasureMM (new) -- (for firstSegment(), see special cases below) -- lastMeasure -- lastMeasureMM (new) -- lastSegment - Segment: -- next (renamed from `next1`) -- nextInMeasure (renamed from `next`) -- prev (renamed from `prev1`) -- prevInMeasure (renamed from prev) Special cases: - Cursor: The prototype of the `Q_INVOKABLE Ms::Note* Cursor::addNote(int pitch)` was wrong: corrected in `Q_INVOKABLE void Cursor::addNote(int pitch)`. - QmlPlugin: `Q_INVOKABLE Score* QmlPlugin::readScore()` and `Q_INVOKABLE Score* QmlPlugin::newScore()` has been kept, as they are intended to be called from QML; code has been added to ensure the C++ ownership of the returned object. - Score: `Q_INVOKABLE Segment* Score::firstSegment(Segment::Type segType)` is kept (as it needs a parameters), but code is added to ensure C++ ownership of the returned Segment*. - Segment: `Ms::Element* Segment::element(int track)` has been made NOT Q_INVOKABLE; a variant `Q_INVOKABLE Ms::Element* elementAt(int track)` has been added specifically for QML with code to ensure the C++ ownership of the returned Element* (this was the cause for the crash of the Walk plug-in). - FiguredBass: `Q_INVOKABLE Ms::FiguredBassItem* FiguredBass::addItem()` has been removed; plugin interface for FiguredBass needs to be redesigned anyway. The few occurrences in the supplied plug-ins of the methods whose names did change have been updated.
2014-07-06 01:56:30 +02:00
#endif
2014-07-06 02:14:58 +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
2013-10-02 10:26:09 +02:00
{
Measure* m = firstMeasureMM();
return m ? m->first(segType) : 0;
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// lastSegment
//---------------------------------------------------------
Segment* Score::lastSegment() const
{
Measure* m = lastMeasure();
return m ? m->last() : 0;
}
//---------------------------------------------------------
// utick2utime
//---------------------------------------------------------
qreal Score::utick2utime(int tick) const
{
return repeatList().utick2utime(tick);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// utime2utick
//---------------------------------------------------------
int Score::utime2utick(qreal utime) const
{
return repeatList().utime2utick(utime);
}
//---------------------------------------------------------
// setExpandRepeats
//---------------------------------------------------------
void MasterScore::setExpandRepeats(bool expand)
{
if (_expandRepeats == expand)
return;
_expandRepeats = expand;
setPlaylistDirty();
}
//---------------------------------------------------------
// updateRepeatListTempo
/// needed for usage in Seq::processMessages
//---------------------------------------------------------
void MasterScore::updateRepeatListTempo()
{
_repeatList->updateTempo();
}
//---------------------------------------------------------
// repeatList
//---------------------------------------------------------
const RepeatList& MasterScore::repeatList() const
{
_repeatList->update(_expandRepeats);
return *_repeatList;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// inputPos
//---------------------------------------------------------
Fraction Score::inputPos() const
2012-05-26 14:26:10 +02:00
{
return _is.tick();
}
//---------------------------------------------------------
// scanElements
// scan all elements
//---------------------------------------------------------
void Score::scanElements(void* data, void (*func)(void*, Element*), bool all)
{
for (MeasureBase* mb = first(); mb; mb = mb->next()) {
mb->scanElements(data, func, all);
2017-01-18 14:16:33 +01:00
if (mb->type() == ElementType::MEASURE) {
2016-06-03 10:17:06 +02:00
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
}
//---------------------------------------------------------
// scanElementsInRange
//---------------------------------------------------------
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()) {
2017-12-20 16:49:30 +01:00
Spanner* spanner = toSpanner(e);
for (SpannerSegment* ss : spanner->spannerSegments()) {
ss->scanElements(data, func, all);
}
}
}
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// setSelection
//---------------------------------------------------------
void Score::setSelection(const Selection& s)
{
deselectAll();
_selection = s;
2013-04-17 10:31:21 +02:00
2012-05-26 14:26:10 +02:00
foreach(Element* e, _selection.elements())
e->setSelected(true);
}
//---------------------------------------------------------
// getText
//---------------------------------------------------------
2018-08-01 11:46:07 +02:00
Text* Score::getText(Tid tid)
2012-05-26 14:26:10 +02:00
{
2012-11-27 13:19:24 +01:00
MeasureBase* m = first();
2018-08-01 11:46:07 +02:00
if (m && m->isVBox()) {
2017-01-16 20:51:12 +01:00
for (Element* e : m->el()) {
2018-08-01 11:46:07 +02:00
if (e->isText() && toText(e)->tid() == tid)
2016-06-03 10:17:06 +02:00
return toText(e);
2012-05-26 14:26:10 +02:00
}
}
return 0;
}
2013-07-19 10:39:32 +02:00
//---------------------------------------------------------
// metaTag
//---------------------------------------------------------
QString Score::metaTag(const QString& s) const
2013-07-19 10:39:32 +02:00
{
2013-07-19 18:03:35 +02:00
if (_metaTags.contains(s))
return _metaTags.value(s);
2016-03-10 10:41:31 +01:00
return _masterScore->_metaTags.value(s);
2013-07-19 10:39:32 +02:00
}
//---------------------------------------------------------
// setMetaTag
//---------------------------------------------------------
void Score::setMetaTag(const QString& tag, const QString& val)
2013-07-19 10:39:32 +02:00
{
2013-07-19 18:03:35 +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)
2012-05-26 14:26:10 +02:00
{
2016-10-10 18:37:28 +02:00
Score* score = ex->partScore();
for (Staff* s : score->staves()) {
2018-04-27 13:29:20 +02:00
const LinkedElements* ls = s->links();
2012-05-26 14:26:10 +02:00
if (ls == 0)
continue;
2018-04-27 13:29:20 +02:00
for (auto le : *ls) {
Staff* ps = toStaff(le);
2012-05-26 14:26:10 +02:00
if (ps->score() == this) {
ex->parts().append(ps->part());
break;
}
}
}
2016-10-10 18:37:28 +02:00
if (ex->tracks().isEmpty()) { // SHOULDN'T HAPPEN, protected in the UI
QMultiMap<int, int> tracks;
2016-07-31 15:23:11 +02:00
for (Staff* s : score->staves()) {
2018-04-27 13:29:20 +02:00
const LinkedElements* ls = s->links();
2016-07-31 15:23:11 +02:00
if (ls == 0)
continue;
2018-04-27 13:29:20 +02:00
for (auto le : *ls) {
Staff* ps = toStaff(le);
2016-07-31 15:23:11 +02:00
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);
}
2016-10-10 18:37:28 +02:00
excerpts().append(ex);
2012-05-26 14:26:10 +02:00
setExcerptsChanged(true);
}
//---------------------------------------------------------
// removeExcerpt
//---------------------------------------------------------
2016-10-10 18:37:28 +02:00
void MasterScore::removeExcerpt(Excerpt* ex)
2012-05-26 14:26:10 +02:00
{
2016-10-10 18:37:28 +02:00
if (excerpts().removeOne(ex)) {
setExcerptsChanged(true);
// delete ex;
2012-05-26 14:26:10 +02:00
}
2016-10-10 18:37:28 +02:00
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()
2012-05-26 14:26:10 +02:00
{
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
2016-11-19 11:51:21 +01:00
XmlWriter xml(this, &buffer);
2012-05-26 14:26:10 +02:00
xml.header();
xml.stag("museScore version=\"" MSC_VERSION "\"");
write(xml, false);
xml.etag();
buffer.close();
XmlReader r(buffer.buffer());
2016-03-10 10:41:31 +01:00
MasterScore* score = new MasterScore(style());
2013-01-11 18:10:18 +01:00
score->read1(r, true);
2012-05-26 14:26:10 +02:00
2016-02-10 13:40:34 +01:00
score->addLayoutFlags(LayoutFlag::FIX_PITCH_VELO);
2012-05-26 14:26:10 +02:00
score->doLayout();
return score;
}
//---------------------------------------------------------
// setSynthesizerState
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
void Score::setSynthesizerState(const SynthesizerState& s)
2012-05-26 14:26:10 +02:00
{
// TODO: make undoable
_synthesizerState = s;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// removeAudio
//---------------------------------------------------------
void Score::removeAudio()
{
delete _audio;
_audio = 0;
}
//---------------------------------------------------------
// appendScore
//---------------------------------------------------------
bool Score::appendScore(Score* score, bool addPageBreak, bool addSectionBreak)
2012-05-26 14:26:10 +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());
2014-11-28 14:49:46 +01:00
return false;
}
2012-05-26 14:26:10 +02:00
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
}
2014-11-28 14:49:46 +01:00
}
else if (!last()->lineBreak() && !last()->pageBreak()) {
last()->undoSetBreak(true, LayoutBreak::Type::LINE);
}
if (addSectionBreak && !last()->sectionBreak())
last()->undoSetBreak(true, LayoutBreak::Type::SECTION);
2014-11-28 14:49:46 +01:00
// match concert pitch states
2018-03-27 15:36:00 +02:00
if (styleB(Sid::concertPitch) != score->styleB(Sid::concertPitch))
score->cmdConcertPitchChanged(styleB(Sid::concertPitch), true);
2014-11-28 14:49:46 +01:00
// clone the measures
appendMeasuresFromScore(score, Fraction(0, 1), score->last()->endTick());
setLayoutAll();
return true;
}
//---------------------------------------------------------
// appendMeasuresFromScore
// clone measures from another score to the end of this
//---------------------------------------------------------
bool Score::appendMeasuresFromScore(Score* score, const Fraction& startTick, const Fraction& endTick)
{
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()) {
2012-05-26 14:26:10 +02:00
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);
2012-05-26 14:26:10 +02:00
nmb->setNext(0);
nmb->setPrev(pmb);
2012-05-26 14:26:10 +02:00
nmb->setScore(this);
pmb->setNext(nmb);
pmb = nmb;
2012-05-26 14:26:10 +02:00
}
Measure* firstAppendedMeasure = tick2measure(tickOfAppend);
2014-11-28 14:49:46 +01:00
// 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) {
2016-06-03 10:17:06 +02:00
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));
}
// 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);
if (ns)
ns->remove(ns->element(trackIdx));
2014-11-28 14:49:46 +01:00
}
}
// 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...
2017-12-20 16:49:30 +01:00
Spanner* ns = toSpanner(spanner->clone());
2014-11-28 14:49:46 +01:00
ns->setScore(this);
ns->setParent(0);
ns->setTick(spanner->tick() - startTick + tickOfAppend);
ns->setTick2(spanner->tick2() - startTick + tickOfAppend);
ns->computeStartElement();
ns->computeEndElement();
2014-11-28 14:49:46 +01:00
addElement(ns);
}
2012-05-26 14:26:10 +02:00
return true;
}
//---------------------------------------------------------
// splitStaff
//---------------------------------------------------------
void Score::splitStaff(int staffIdx, int splitPoint)
{
2016-04-11 15:28:32 +02:00
// qDebug("split staff %d point %d", staffIdx, splitPoint);
2012-05-26 14:26:10 +02:00
//
// create second staff
//
Staff* st = staff(staffIdx);
Part* p = st->part();
2014-08-16 13:32:08 +02:00
Staff* ns = new Staff(this);
2018-11-26 02:16:45 +01:00
ns->init(st);
2014-08-16 13:32:08 +02:00
ns->setPart(p);
// convert staffIdx from score-relative to part-relative
int staffIdxPart = staffIdx - p->staff(0)->idx();
undoInsertStaff(ns, staffIdxPart + 1, false);
2012-05-26 14:26:10 +02:00
Clef* clef = new Clef(this);
2013-09-05 16:37:49 +02:00
clef->setClefType(ClefType::F);
2012-05-26 14:26:10 +02:00
clef->setTrack((staffIdx+1) * VOICES);
Segment* seg = firstMeasure()->getSegment(SegmentType::HeaderClef, Fraction(0, 1));
2012-05-26 14:26:10 +02:00
clef->setParent(seg);
undoAddElement(clef);
2016-01-04 14:48:58 +01:00
clef->layout();
2012-05-26 14:26:10 +02:00
undoChangeKeySig(ns, Fraction(0, 1), st->keySigEvent(Fraction(0, 1)));
2012-05-26 14:26:10 +02:00
2016-03-11 12:18:46 +01:00
masterScore()->rebuildMidiMapping();
2016-03-18 09:29:16 +01:00
cmdState()._instrumentsChanged = true;
2012-05-26 14:26:10 +02:00
doLayout();
//
// move notes
//
select(0, SelectType::SINGLE, 0);
2012-05-26 14:26:10 +02:00
int strack = staffIdx * VOICES;
int dtrack = (staffIdx + 1) * VOICES;
2017-03-08 13:12:26 +01:00
for (Segment* s = firstSegment(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
2012-05-26 14:26:10 +02:00
for (int voice = 0; voice < VOICES; ++voice) {
2016-06-03 10:49:40 +02:00
Element* e = s->element(strack + voice);
if (!(e && e->isChord()))
2012-05-26 14:26:10 +02:00
continue;
2016-06-03 10:49:40 +02:00
Chord* c = toChord(e);
2012-05-26 14:26:10 +02:00
QList<Note*> removeNotes;
foreach(Note* note, c->notes()) {
if (note->pitch() >= splitPoint)
continue;
2016-06-03 10:17:06 +02:00
Chord* chord = toChord(s->element(dtrack + voice));
Q_ASSERT(!chord || (chord->isChord()));
2012-05-26 14:26:10 +02:00
if (chord == 0) {
chord = new Chord(*c);
2014-08-16 16:10:47 +02:00
qDeleteAll(chord->notes());
2012-05-26 14:26:10 +02:00
chord->notes().clear();
chord->setTrack(dtrack + voice);
undoAddElement(chord);
}
Note* nnote = new Note(*note);
nnote->setTrack(dtrack + voice);
chord->add(nnote);
nnote->updateLine();
2012-05-26 14:26:10 +02:00
removeNotes.append(note);
}
2014-04-09 16:09:21 +02:00
c->sortNotes();
for (Note* note : removeNotes) {
2012-05-26 14:26:10 +02:00
undoRemoveElement(note);
Chord* chord = note->chord();
2016-02-06 22:03:43 +01:00
if (chord->notes().empty()) {
for (auto sp : spanner()) {
2017-12-20 16:49:30 +01:00
Slur* slur = toSlur(sp.second);
2017-01-18 14:16:33 +01:00
if (slur->type() != ElementType::SLUR)
continue;
if (slur->startCR() == chord) {
2018-03-27 15:36:00 +02:00
slur->undoChangeProperty(Pid::TRACK, slur->track()+VOICES);
for (ScoreElement* ee : slur->linkList()) {
2017-12-20 16:49:30 +01:00
Slur* lslur = toSlur(ee);
lslur->setStartElement(0);
}
}
if (slur->endCR() == chord) {
2018-03-27 15:36:00 +02:00
slur->undoChangeProperty(Pid::SPANNER_TRACK2, slur->track2()+VOICES);
for (ScoreElement* ee : slur->linkList()) {
2017-12-20 16:49:30 +01:00
Slur* lslur = toSlur(ee);
lslur->setEndElement(0);
}
}
}
undoRemoveElement(chord);
}
2012-05-26 14:26:10 +02:00
}
}
}
//
// make sure that the timeline for dtrack
// has no gaps
//
Fraction ctick = Fraction(0,1);
2012-05-26 14:26:10 +02:00
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
2017-03-08 13:12:26 +01:00
for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
2016-06-03 10:17:06 +02:00
ChordRest* cr = toChordRest(s->element(dtrack));
2012-05-26 14:26:10 +02:00
if (cr == 0)
continue;
Fraction rest = s->tick() - ctick;
if (rest.isNotZero()) {
2012-05-26 14:26:10 +02:00
// insert Rest
Segment* s1 = tick2segment(ctick);
if (s1 == 0) {
qDebug("no segment at %d", ctick.ticks());
2012-05-26 14:26:10 +02:00
continue;
}
setRest(ctick, dtrack, rest, false, 0);
2012-05-26 14:26:10 +02:00
}
ctick = s->tick() + cr->actualTicks();
}
Fraction rest = m->tick() + m->ticks() - ctick;
if (rest.isNotZero()) {
setRest(ctick, dtrack, rest, false, 0);
2012-05-26 14:26:10 +02:00
ctick += rest;
}
}
//
// same for strack
//
ctick = Fraction(0,1);
2012-05-26 14:26:10 +02:00
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
2017-03-08 13:12:26 +01:00
for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
2016-06-03 10:17:06 +02:00
ChordRest* cr = toChordRest(s->element(strack));
2012-05-26 14:26:10 +02:00
if (cr == 0)
continue;
Fraction rest = s->tick() - ctick;
if (rest.isNotZero()) {
2012-05-26 14:26:10 +02:00
// insert Rest
Segment* s1 = tick2segment(ctick);
if (s1 == 0) {
qDebug("no segment at %d", ctick.ticks());
2012-05-26 14:26:10 +02:00
continue;
}
setRest(ctick, strack, rest, false, 0);
2012-05-26 14:26:10 +02:00
}
ctick = s->tick() + cr->actualTicks();
}
Fraction rest = m->tick() + m->ticks() - ctick;
if (rest.isNotZero()) {
setRest(ctick, strack, rest, false, 0);
2012-05-26 14:26:10 +02:00
ctick += rest;
}
}
}
//---------------------------------------------------------
// cmdRemovePart
//---------------------------------------------------------
void Score::cmdRemovePart(Part* part)
{
int sidx = staffIdx(part);
int n = part->nstaves();
2014-08-16 14:18:06 +02:00
2012-05-26 14:26:10 +02:00
for (int i = 0; i < n; ++i)
cmdRemoveStaff(sidx);
2012-05-26 14:26:10 +02:00
undoRemovePart(part, sidx);
}
//---------------------------------------------------------
// insertPart
//---------------------------------------------------------
void Score::insertPart(Part* part, int idx)
{
bool inserted = false;
2012-05-26 14:26:10 +02:00
int staff = 0;
for (QList<Part*>::iterator i = _parts.begin(); i != _parts.end(); ++i) {
if (staff >= idx) {
_parts.insert(i, part);
inserted = true;
break;
2012-05-26 14:26:10 +02:00
}
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)
{
_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)
2012-05-26 14:26:10 +02:00
{
2014-08-11 15:25:55 +02:00
staff->part()->insertStaff(staff, ridx);
2014-08-16 13:32:08 +02:00
2014-08-16 14:18:06 +02:00
int idx = staffIdx(staff->part()) + ridx;
_staves.insert(idx, staff);
2014-08-16 13:32:08 +02:00
for (auto i = staff->score()->spanner().cbegin(); i != staff->score()->spanner().cend(); ++i) {
Spanner* s = i->second;
2017-06-02 10:27:32 +02:00
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());
}
}
}
#if 0
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
2012-05-26 14:26:10 +02:00
}
2014-08-16 14:18:06 +02:00
//---------------------------------------------------------
// removeStaff
//---------------------------------------------------------
void Score::removeStaff(Staff* staff)
{
2016-09-28 21:13:05 +02:00
int idx = staff->idx();
2014-08-16 14:18:06 +02:00
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;
2017-06-02 10:27:32 +02:00
if (t < 0)
t = 0;
s->setTrack(t);
for (SpannerSegment* ss : s->spannerSegments())
ss->setTrack(t);
2014-08-16 14:18:06 +02:00
if (s->track2() != -1) {
t = s->track2() - VOICES;
2017-06-02 10:27:32 +02:00
s->setTrack2(t >= 0 ? t : s->track());
2014-08-16 14:18:06 +02:00
}
}
}
2017-06-02 10:27:32 +02:00
#if 0
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());
}
}
}
#endif
2014-08-16 14:18:06 +02:00
_staves.removeAll(staff);
staff->part()->removeStaff(staff);
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// adjustBracketsDel
//---------------------------------------------------------
void Score::adjustBracketsDel(int sidx, int eidx)
{
for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
Staff* staff = _staves[staffIdx];
2017-03-31 13:03:15 +02:00
for (BracketItem* bi : staff->brackets()) {
int span = bi->bracketSpan();
2012-05-26 14:26:10 +02:00
if ((span == 0) || ((staffIdx + span) < sidx) || (staffIdx > eidx))
continue;
if ((sidx >= staffIdx) && (eidx <= (staffIdx + span)))
2018-03-27 15:36:00 +02:00
bi->undoChangeProperty(Pid::BRACKET_SPAN, span - (eidx-sidx));
2012-05-26 14:26:10 +02:00
}
2016-01-04 14:48:58 +01:00
#if 0 // TODO
2016-02-09 13:51:19 +01:00
int span = staff->barLineSpan();
2012-10-14 00:35:11 +02:00
if ((sidx >= staffIdx) && (eidx <= (staffIdx + span))) {
int newSpan = span - (eidx-sidx) + 1;
2012-10-14 00:35:11 +02:00
int lastSpannedStaffIdx = staffIdx + newSpan - 1;
2017-03-31 13:03:15 +02:00
int tick = 0;
undoChangeBarLineSpan(staff, newSpan, 0, (_staves[lastSpannedStaffIdx]->lines(0)-1)*2);
2012-10-14 00:35:11 +02:00
}
2016-01-04 14:48:58 +01:00
#endif
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// adjustBracketsIns
//---------------------------------------------------------
void Score::adjustBracketsIns(int sidx, int eidx)
{
for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
Staff* staff = _staves[staffIdx];
2017-03-31 13:03:15 +02:00
for (BracketItem* bi : staff->brackets()) {
int span = bi->bracketSpan();
2012-05-26 14:26:10 +02:00
if ((span == 0) || ((staffIdx + span) < sidx) || (staffIdx > eidx))
continue;
if ((sidx >= staffIdx) && (eidx < (staffIdx + span)))
2018-03-27 15:36:00 +02:00
bi->undoChangeProperty(Pid::BRACKET_SPAN, span + (eidx-sidx));
2012-05-26 14:26:10 +02:00
}
2016-02-09 13:51:19 +01:00
#if 0 // TODO
2012-05-26 14:26:10 +02:00
int span = staff->barLineSpan();
2014-08-16 17:54:10 +02:00
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);
}
2016-01-04 14:48:58 +01:00
#endif
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// adjustKeySigs
//---------------------------------------------------------
void Score::adjustKeySigs(int sidx, int eidx, KeyList km)
{
for (int staffIdx = sidx; staffIdx < eidx; ++staffIdx) {
Staff* staff = _staves[staffIdx];
2014-06-05 11:37:21 +02:00
for (auto i = km.begin(); i != km.end(); ++i) {
Fraction tick = Fraction::fromTicks(i->first);
2014-06-05 11:37:21 +02:00
Measure* measure = tick2measure(tick);
if (!measure)
continue;
2016-12-13 13:16:17 +01:00
if (staff->isDrumStaff(tick))
continue;
KeySigEvent oKey = i->second;
KeySigEvent nKey = oKey;
int diff = -staff->part()->instrument(tick)->transpose().chromatic;
2018-03-27 15:36:00 +02:00
if (diff != 0 && !styleB(Sid::concertPitch) && !oKey.custom() && !oKey.isAtonal())
nKey.setKey(transposeKey(nKey.key(), diff));
2014-06-05 11:37:21 +02:00
staff->setKey(tick, nKey);
KeySig* keysig = new KeySig(this);
keysig->setTrack(staffIdx * VOICES);
keysig->setKeySigEvent(nKey);
2017-03-08 13:12:26 +01:00
Segment* s = measure->getSegment(SegmentType::KeySig, tick);
2014-06-05 11:37:21 +02:00
s->add(keysig);
}
}
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// cmdRemoveStaff
//---------------------------------------------------------
void Score::cmdRemoveStaff(int staffIdx)
{
Staff* s = staff(staffIdx);
adjustBracketsDel(staffIdx, staffIdx+1);
2013-09-19 18:24:58 +02:00
2014-08-11 15:25:55 +02:00
undoRemoveStaff(s);
// remove linked staff and measures in linked staves in excerpts
// unlink staff in the same score
2018-04-27 13:29:20 +02:00
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);
2018-04-27 13:29:20 +02:00
s->score()->undo(new Unlink(staff));
2014-08-25 19:30:56 +02:00
if (staff->part()->nstaves() == 0) {
int pIndex = lscore->staffIdx(staff->part());
lscore->undoRemovePart(staff->part(), pIndex);
2014-08-25 19:30:56 +02:00
}
}
else // linked staff in the same score
2016-03-21 18:39:29 +01:00
sameScoreLinkedStaff = staff;
}
2016-03-21 18:39:29 +01:00
if (sameScoreLinkedStaff)
2018-04-27 13:29:20 +02:00
// s->score()->undo(new Unlink(sameScoreLinkedStaff)); // once should be enough
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)
{
qDeleteAll(systems());
2016-02-04 11:27:47 +01:00
systems().clear(); //??
2012-05-26 14:26:10 +02:00
_parts.clear();
Part* curPart = 0;
QList<Staff*> dl;
foreach (int idx, dst) {
2012-05-26 14:26:10 +02:00
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()) {
2012-05-26 14:26:10 +02:00
m->sortStaves(dst);
if (m->hasMMRest())
m->mmRest()->sortStaves(dst);
2012-05-26 14:26:10 +02:00
}
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*/)
2012-05-26 14:26:10 +02:00
{
2018-03-27 15:36:00 +02:00
undoChangeStyleVal(Sid::concertPitch, flag); // change style flag
2012-05-26 14:26:10 +02:00
2014-04-02 18:11:56 +02:00
for (Staff* staff : _staves) {
if (staff->staffType(Fraction(0,1))->group() == StaffGroup::PERCUSSION) // TODO
2012-05-26 14:26:10 +02:00
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)
2012-05-26 14:26:10 +02:00
continue;
if (!flag)
interval.flip();
2014-04-02 18:11:56 +02:00
int staffIdx = staff->idx();
2014-04-02 18:11:56 +02:00
int startTrack = staffIdx * VOICES;
int endTrack = startTrack + VOICES;
transposeKeys(staffIdx, staffIdx + 1, Fraction(0,1), lastSegment()->tick(), interval, true, !flag);
2014-04-02 18:11:56 +02:00
2017-03-08 13:12:26 +01:00
for (Segment* segment = firstSegment(SegmentType::ChordRest); segment; segment = segment->next1(SegmentType::ChordRest)) {
interval = staff->part()->instrument(segment->tick())->transpose();
if (!flag)
interval.flip();
2014-04-02 18:11:56 +02:00
for (Element* e : segment->annotations()) {
if (!e->isHarmony() || (e->track() < startTrack) || (e->track() >= endTrack))
2014-04-02 18:11:56 +02:00
continue;
2016-06-03 10:17:06 +02:00
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);
}
2014-04-02 18:11:56 +02:00
}
2012-05-26 14:26:10 +02:00
}
}
}
//---------------------------------------------------------
// addAudioTrack
//---------------------------------------------------------
void Score::addAudioTrack()
{
// TODO
}
//---------------------------------------------------------
// padToggle
//---------------------------------------------------------
void Score::padToggle(Pad n, const EditData& ed)
2012-05-26 14:26:10 +02:00
{
2016-08-08 17:45:53 +02:00
int oldDots = _is.duration().dots();
2012-05-26 14:26:10 +02:00
switch (n) {
case Pad::NOTE00:
_is.setDuration(TDuration::DurationType::V_LONG);
2012-05-26 14:26:10 +02:00
break;
case Pad::NOTE0:
_is.setDuration(TDuration::DurationType::V_BREVE);
2012-05-26 14:26:10 +02:00
break;
case Pad::NOTE1:
_is.setDuration(TDuration::DurationType::V_WHOLE);
2012-05-26 14:26:10 +02:00
break;
case Pad::NOTE2:
_is.setDuration(TDuration::DurationType::V_HALF);
2012-05-26 14:26:10 +02:00
break;
case Pad::NOTE4:
_is.setDuration(TDuration::DurationType::V_QUARTER);
2012-05-26 14:26:10 +02:00
break;
case Pad::NOTE8:
_is.setDuration(TDuration::DurationType::V_EIGHTH);
2012-05-26 14:26:10 +02:00
break;
case Pad::NOTE16:
_is.setDuration(TDuration::DurationType::V_16TH);
2012-05-26 14:26:10 +02:00
break;
case Pad::NOTE32:
_is.setDuration(TDuration::DurationType::V_32ND);
2012-05-26 14:26:10 +02:00
break;
case Pad::NOTE64:
_is.setDuration(TDuration::DurationType::V_64TH);
2012-05-26 14:26:10 +02:00
break;
case Pad::NOTE128:
_is.setDuration(TDuration::DurationType::V_128TH);
2012-05-26 14:26:10 +02:00
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());
}
}
2012-05-26 14:26:10 +02:00
break;
case Pad::DOT:
if ((_is.duration().dots() == 1) || (_is.duration() == TDuration::DurationType::V_1024TH))
2012-05-26 14:26:10 +02:00
_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))
2012-05-26 14:26:10 +02:00
_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))
_is.setDots(0);
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;
2012-05-26 14:26:10 +02:00
}
if (n >= Pad::NOTE00 && n <= Pad::NOTE128) {
2012-05-26 14:26:10 +02:00
_is.setDots(0);
//
// if in "note enter" mode, reset
// rest flag
//
2016-08-08 17:45:53 +02:00
if (noteEntryMode()) {
if (usingNoteEntryMethod(NoteEntryMethod::RHYTHM)) {
switch (oldDots) {
case 1:
padToggle(Pad::DOT, ed);
2016-08-08 17:45:53 +02:00
break;
case 2:
padToggle(Pad::DOTDOT, ed);
2016-08-08 17:45:53 +02:00
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();
2016-08-08 17:45:53 +02:00
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);
}
2012-05-26 14:26:10 +02:00
}
if (noteEntryMode())
2012-05-26 14:26:10 +02:00
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);
}
2012-05-26 14:26:10 +02:00
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();
}
2012-05-26 14:26:10 +02:00
for (ChordRest* cr : crs) {
if (cr->isChord() && (toChord(cr)->isGrace())) {
//
// handle appoggiatura and acciaccatura
//
undoChangeChordRestLen(cr, _is.duration());
}
else
changeCRlen(cr, _is.duration());
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// deselect
//---------------------------------------------------------
void Score::deselect(Element* el)
{
2016-03-18 09:29:16 +01:00
addRefresh(el->abbox());
2012-05-26 14:26:10 +02:00
_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)
{
if (e && (e->isNote() || e->isRest())) {
2012-05-26 14:26:10 +02:00
Element* ee = e;
if (ee->isNote())
2012-05-26 14:26:10 +02:00
ee = ee->parent();
Fraction tick = toChordRest(ee)->segment()->tick();
if (masterScore()->playPos() != tick)
masterScore()->setPlayPos(tick);
2012-05-26 14:26:10 +02:00
}
if (MScore::debugMode)
2015-10-27 11:30:09 +01:00
qDebug("select element <%s> type %d(state %d) staff %d",
e ? e->name() : "", int(type), int(selection().state()), e ? e->staffIdx() : -1);
2012-05-26 14:26:10 +02:00
2014-05-24 22:24:48 +02:00
switch (type) {
case SelectType::SINGLE:
selectSingle(e, staffIdx);
break;
case SelectType::ADD:
selectAdd(e);
break;
case SelectType::RANGE:
selectRange(e, staffIdx);
break;
2014-05-24 22:24:48 +02:00
}
2017-03-31 13:03:15 +02:00
setSelectionChanged(true);
2014-05-24 22:24:48 +02:00
}
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)
{
SelState selState = _selection.state();
deselectAll();
if (e == 0) {
selState = SelState::NONE;
2016-03-02 13:20:19 +01:00
setUpdateAll();
2014-05-24 22:24:48 +02:00
}
else {
2017-03-31 13:03:15 +02:00
if (e->isMeasure()) {
2014-05-24 22:24:48 +02:00
select(e, SelectType::RANGE, staffIdx);
return;
2012-05-26 14:26:10 +02:00
}
2016-03-18 09:29:16 +01:00
addRefresh(e->abbox());
2014-05-24 22:24:48 +02:00
_selection.add(e);
_is.setTrack(e->track());
selState = SelState::LIST;
2017-01-18 14:16:33 +01:00
if (e->type() == ElementType::NOTE) {
2014-05-24 22:24:48 +02:00
e = e->parent();
}
2017-03-31 13:03:15 +02:00
if (e->isChordRest()) {
2014-05-24 22:24:48 +02:00
_is.setLastSegment(_is.segment());
2016-06-03 10:17:06 +02:00
_is.setSegment(toChordRest(e)->segment());
2012-05-26 14:26:10 +02:00
}
}
2014-05-24 22:24:48 +02:00
_selection.setActiveSegment(0);
_selection.setActiveTrack(0);
_selection.setState(selState);
}
//---------------------------------------------------------
// switchToPageMode
//---------------------------------------------------------
void Score::switchToPageMode()
{
if (_layoutMode != LayoutMode::PAGE) {
setLayoutMode(LayoutMode::PAGE);
doLayout();
}
}
2014-05-24 22:24:48 +02:00
//---------------------------------------------------------
// selectAdd
//---------------------------------------------------------
void Score::selectAdd(Element* e)
{
SelState selState = _selection.state();
if (_selection.isRange()) {
select(0, SelectType::SINGLE, 0);
return;
}
if (e->isMeasure()) {
2016-06-03 10:17:06 +02:00
Measure* m = toMeasure(e);
Fraction tick = m->tick();
2014-05-24 22:24:48 +02:00
if (_selection.isNone()) {
_selection.setRange(m->tick2segment(tick),
m == lastMeasure() ? 0 : m->last(),
0,
nstaves());
setUpdateAll();
selState = SelState::RANGE;
_selection.updateSelectedElements();
2012-05-26 14:26:10 +02:00
}
}
else if (!_selection.elements().contains(e)) {
2016-03-18 09:29:16 +01:00
addRefresh(e->abbox());
selState = SelState::LIST;
_selection.add(e);
2014-05-24 22:24:48 +02:00
}
2014-05-24 22:24:48 +02:00
_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)
{
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();
2014-05-24 22:24:48 +02:00
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);
2014-05-24 22:24:48 +02:00
}
else if (_selection.isRange())
_selection.extendRangeSelection(s1, s2, staffIdx, tick, etick);
2014-05-24 22:24:48 +02:00
else if (_selection.isSingle()) {
Element* oe = selection().element();
2017-01-18 14:16:33 +01:00
if (oe->isNote() || oe->isChordRest()) {
if (oe->isNote())
oe = oe->parent();
2016-06-03 10:17:06 +02:00
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);
2012-05-26 14:26:10 +02:00
}
else {
deselectAll();
_selection.setRange(s1, s2, staffIdx, staffIdx + 1);
2012-05-26 14:26:10 +02:00
}
2014-05-24 22:24:48 +02:00
}
else {
2015-10-27 11:30:09 +01:00
qDebug("SELECT_RANGE: measure: sel state %d", int(_selection.state()));
2014-05-24 22:24:48 +02:00
return;
2012-05-26 14:26:10 +02:00
}
2014-05-24 22:24:48 +02:00
}
else if (e->isNote() || e->isChordRest()) {
if (e->isNote())
2014-05-24 22:24:48 +02:00
e = e->parent();
2016-06-03 10:17:06 +02:00
ChordRest* cr = toChordRest(e);
2012-05-26 14:26:10 +02:00
if (_selection.isNone() || (_selection.isList() && !_selection.isSingle())) {
2014-05-31 21:14:25 +02:00
if (_selection.isList())
deselectAll();
SegmentType st = SegmentType::ChordRest | SegmentType::EndBarLine | SegmentType::Clef;
_selection.setRange(cr->segment(), cr->nextSegmentAfterCR(st), e->staffIdx(), e->staffIdx() + 1);
2014-05-24 22:24:48 +02:00
activeTrack = cr->track();
}
else if (_selection.isSingle()) {
Element* oe = _selection.element();
if (oe && (oe->isNote() || oe->isRest())) {
if (oe->isNote())
2014-05-24 22:24:48 +02:00
oe = oe->parent();
2016-06-03 10:17:06 +02:00
ChordRest* ocr = toChordRest(oe);
2014-05-31 21:14:25 +02:00
Segment* endSeg = tick2segmentMM(ocr->segment()->tick() + ocr->actualTicks());
2014-05-31 21:14:25 +02:00
if (!endSeg)
endSeg = ocr->segment()->next();
_selection.setRange(ocr->segment(), endSeg, oe->staffIdx(), oe->staffIdx() + 1);
2014-05-31 21:14:25 +02:00
_selection.extendRangeSelection(cr);
2012-05-26 14:26:10 +02:00
}
else {
2014-05-24 22:24:48 +02:00
select(e, SelectType::SINGLE, 0);
return;
2012-05-26 14:26:10 +02:00
}
2014-05-24 22:24:48 +02:00
}
else if (_selection.isRange()) {
2014-05-31 21:14:25 +02:00
_selection.extendRangeSelection(cr);
2012-05-26 14:26:10 +02:00
}
else {
2015-10-27 11:30:09 +01:00
qDebug("sel state %d", int(_selection.state()));
2012-05-26 14:26:10 +02:00
return;
}
if (!endRangeSelected && !_selection.endSegment())
2014-05-24 22:24:48 +02:00
_selection.setEndSegment(cr->segment()->nextCR());
if (!_selection.startSegment())
_selection.setStartSegment(cr->segment());
}
else {
select(e, SelectType::SINGLE, staffIdx);
return;
}
2012-05-26 14:26:10 +02:00
2014-05-24 22:24:48 +02:00
_selection.setActiveTrack(activeTrack);
2012-05-26 14:26:10 +02:00
// 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);
}
2014-05-24 22:24:48 +02:00
_selection.updateSelectedElements();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// collectMatch
//---------------------------------------------------------
void Score::collectMatch(void* data, Element* e)
{
ElementPattern* p = static_cast<ElementPattern*>(data);
if (p->type != int(e->type()))
return;
2017-01-18 14:16:33 +01:00
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()))
return;
}
else if (p->subtypeValid && p->subtype != e->subtype())
return;
2014-09-23 18:07:32 +02:00
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 {
2017-01-18 14:16:33 +01:00
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())
return;
}
p->el.append(e);
}
//---------------------------------------------------------
// collectNoteMatch
//---------------------------------------------------------
void Score::collectNoteMatch(void* data, Element* e)
{
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);
}
2014-08-15 17:20:20 +02:00
//---------------------------------------------------------
// selectSimilar
//---------------------------------------------------------
void Score::selectSimilar(Element* e, bool sameStaff)
{
2017-01-18 14:16:33 +01:00
ElementType type = e->type();
2014-08-15 17:20:20 +02:00
Score* score = e->score();
ElementPattern pattern;
2014-09-23 18:07:32 +02:00
pattern.type = int(type);
pattern.subtype = 0;
pattern.subtypeValid = false;
2017-01-18 14:16:33 +01:00
if (type == ElementType::NOTE) {
if (toNote(e)->chord()->isGrace())
pattern.subtype = -1; // hack
else
pattern.subtype = e->subtype();
2014-09-23 18:07:32 +02:00
}
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-08-15 17:20:20 +02:00
//---------------------------------------------------------
// selectSimilarInRange
//---------------------------------------------------------
void Score::selectSimilarInRange(Element* e)
{
2017-01-18 14:16:33 +01:00
ElementType type = e->type();
Score* score = e->score();
ElementPattern pattern;
pattern.type = int(type);
pattern.subtype = 0;
pattern.subtypeValid = false;
2017-01-18 14:16:33 +01:00
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
//---------------------------------------------------------
2012-05-26 14:26:10 +02:00
// lassoSelect
//---------------------------------------------------------
void Score::lassoSelect(const QRectF& bbox)
{
select(0, SelectType::SINGLE, 0);
2012-05-26 14:26:10 +02:00
QRectF fr(bbox.normalized());
2017-01-05 11:23:47 +01:00
foreach(Page* page, pages()) {
2012-05-26 14:26:10 +02:00
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);
2012-05-26 14:26:10 +02:00
for (int i = 0; i < el.size(); ++i) {
Element* e = el.at(i);
2012-05-26 14:26:10 +02:00
if (frr.contains(e->abbox())) {
2017-01-18 14:16:33 +01:00
if (e->type() != ElementType::MEASURE && e->selectable())
select(e, SelectType::ADD, 0);
2012-05-26 14:26:10 +02:00
}
}
}
}
//---------------------------------------------------------
// lassoSelectEnd
//---------------------------------------------------------
void Score::lassoSelectEnd()
{
int noteRestCount = 0;
Segment* startSegment = 0;
Segment* endSegment = 0;
int startStaff = 0x7fffffff;
int endStaff = 0;
const ChordRest* endCR = 0;
2012-05-26 14:26:10 +02:00
2016-02-06 22:03:43 +01:00
if (_selection.elements().empty()) {
2014-05-26 13:21:04 +02:00
_selection.setState(SelState::NONE);
2016-03-02 13:20:19 +01:00
setUpdateAll();
2012-05-26 14:26:10 +02:00
return;
}
2014-05-26 13:21:04 +02:00
_selection.setState(SelState::LIST);
2012-05-26 14:26:10 +02:00
foreach(const Element* e, _selection.elements()) {
2017-01-18 14:16:33 +01:00
if (e->type() != ElementType::NOTE && e->type() != ElementType::REST)
2012-05-26 14:26:10 +02:00
continue;
++noteRestCount;
2017-01-18 14:16:33 +01:00
if (e->type() == ElementType::NOTE)
2012-05-26 14:26:10 +02:00
e = e->parent();
Segment* seg = static_cast<const ChordRest*>(e)->segment();
2012-08-09 12:12:43 +02:00
if ((startSegment == 0) || (*seg < *startSegment))
2012-05-26 14:26:10 +02:00
startSegment = seg;
2012-08-09 12:12:43 +02:00
if ((endSegment == 0) || (*seg > *endSegment)) {
2012-05-26 14:26:10 +02:00
endSegment = seg;
2014-05-31 17:22:45 +02:00
endCR = static_cast<const ChordRest*>(e);
2012-05-26 14:26:10 +02:00
}
int idx = e->staffIdx();
if (idx < startStaff)
startStaff = idx;
if (idx > endStaff)
endStaff = idx;
}
if (noteRestCount > 0) {
2017-03-08 13:12:26 +01:00
endSegment = endCR->nextSegmentAfterCR(SegmentType::ChordRest
| SegmentType::EndBarLine
| SegmentType::Clef);
2012-05-26 14:26:10 +02:00
_selection.setRange(startSegment, endSegment, startStaff, endStaff+1);
2014-05-24 12:53:50 +02:00
if (!_selection.isRange())
2014-05-26 13:21:04 +02:00
_selection.setState(SelState::RANGE);
_selection.updateSelectedElements();
2012-05-26 14:26:10 +02:00
}
2016-03-02 13:20:19 +01:00
setUpdateAll();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// addLyrics
//---------------------------------------------------------
void Score::addLyrics(const Fraction& tick, int staffIdx, const QString& txt)
2012-05-26 14:26:10 +02:00
{
if (txt.trimmed().isEmpty())
return;
Measure* measure = tick2measure(tick);
2017-03-08 13:12:26 +01:00
Segment* seg = measure->findSegment(SegmentType::ChordRest, tick);
2012-05-26 14:26:10 +02:00
if (seg == 0) {
2013-07-25 17:22:49 +02:00
qDebug("no segment found for lyrics<%s> at tick %d",
qPrintable(txt), tick.ticks());
2012-05-26 14:26:10 +02:00
return;
}
bool lyricsAdded = false;
for (int voice = 0; voice < VOICES; ++voice) {
int track = staffIdx * VOICES + voice;
2016-06-03 10:17:06 +02:00
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;
}
2012-05-26 14:26:10 +02:00
}
if (!lyricsAdded) {
2013-07-25 17:22:49 +02:00
qDebug("no chord/rest for lyrics<%s> at tick %d, staff %d",
qPrintable(txt), tick.ticks(), staffIdx);
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// setTempo
// convenience function to access TempoMap
//---------------------------------------------------------
void Score::setTempo(Segment* segment, qreal tempo)
{
setTempo(segment->tick(), tempo);
}
void Score::setTempo(const Fraction& tick, qreal tempo)
2012-05-26 14:26:10 +02:00
{
tempomap()->setTempo(tick.ticks(), tempo);
setPlaylistDirty();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// removeTempo
//---------------------------------------------------------
void Score::removeTempo(const Fraction& tick)
2012-05-26 14:26:10 +02:00
{
tempomap()->delTempo(tick.ticks());
setPlaylistDirty();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// resetTempo
//---------------------------------------------------------
void Score::resetTempo()
{
resetTempoRange(Fraction(0,1), Fraction(std::numeric_limits<int>::max(), 1));
}
//---------------------------------------------------------
// resetTempoRange
// Reset tempo and timesig maps in the given range.
// Start tick included, end tick excluded.
//---------------------------------------------------------
void Score::resetTempoRange(const Fraction& tick1, const Fraction& tick2)
{
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));
}
}
//---------------------------------------------------------
2012-05-26 14:26:10 +02:00
// setPause
//---------------------------------------------------------
void Score::setPause(const Fraction& tick, qreal seconds)
2012-05-26 14:26:10 +02:00
{
tempomap()->setPause(tick.ticks(), seconds);
setPlaylistDirty();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// tempo
//---------------------------------------------------------
qreal Score::tempo(const Fraction& tick) const
2012-05-26 14:26:10 +02:00
{
return tempomap()->tempo(tick.ticks());
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// loWidth
//---------------------------------------------------------
qreal Score::loWidth() const
{
2018-03-27 15:36:00 +02:00
return styleD(Sid::pageWidth) * DPI;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// loHeight
//---------------------------------------------------------
qreal Score::loHeight() const
{
2018-03-27 15:36:00 +02:00
return styleD(Sid::pageHeight) * DPI;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// cmdSelectAll
//---------------------------------------------------------
void Score::cmdSelectAll()
{
2013-10-06 16:43:43 +02:00
if (_measures.size() == 0)
return;
2014-08-20 19:18:27 +02:00
deselectAll();
Measure* first = firstMeasureMM();
if (!first)
return;
Measure* last = lastMeasureMM();
selectRange(first, 0);
selectRange(last, nstaves() - 1);
2016-03-02 13:20:19 +01:00
setUpdateAll();
update();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// cmdSelectSection
//---------------------------------------------------------
void Score::cmdSelectSection()
{
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();
}
2017-01-18 14:16:33 +01:00
while (sm && sm->type() != ElementType::MEASURE)
2012-05-26 14:26:10 +02:00
sm = sm->next();
2017-01-18 14:16:33 +01:00
while (em && em->type() != ElementType::MEASURE)
2012-05-26 14:26:10 +02:00
em = em->next();
if (sm == 0 || em == 0)
return;
2016-06-03 10:17:06 +02:00
_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
2012-05-26 14:26:10 +02:00
{
2017-05-19 09:06:41 +02:00
undoStack()->push(cmd, ed);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// linkId
//---------------------------------------------------------
int Score::linkId()
{
2016-03-10 10:41:31 +01: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)
{
2016-03-10 10:41:31 +01:00
Score* s = masterScore();
2012-09-22 19:40:45 +02:00
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()
{
QList<Score*> scores;
2016-03-10 10:41:31 +01:00
Score* root = masterScore();
2012-05-26 14:26:10 +02:00
scores.append(root);
2014-11-27 14:50:02 +01:00
for (const Excerpt* ex : root->excerpts()) {
if (ex->partScore())
scores.append(ex->partScore());
}
2012-05-26 14:26:10 +02:00
return scores;
}
//---------------------------------------------------------
// switchLayer
//---------------------------------------------------------
bool Score::switchLayer(const QString& s)
{
int layerIdx = 0;
2014-05-31 12:03:21 +02:00
for (const Layer& l : layer()) {
2012-05-26 14:26:10 +02:00
if (s == l.name) {
setCurrentLayer(layerIdx);
return true;
}
++layerIdx;
}
return false;
}
2012-06-07 09:24:05 +02:00
//---------------------------------------------------------
// appendPart
//---------------------------------------------------------
void Score::appendPart(const QString& name)
{
static InstrumentTemplate defaultInstrument;
InstrumentTemplate* t;
t = searchTemplate(name);
2012-06-08 19:06:52 +02:00
if (t == 0) {
qDebug("appendPart: <%s> not found", qPrintable(name));
2012-06-07 09:24:05 +02:00
t = &defaultInstrument;
2012-06-08 19:06:52 +02:00
}
2012-06-07 09:24:05 +02:00
2016-02-06 22:03:43 +01:00
if (t->channel.empty()) {
2012-06-07 09:24:05 +02:00
Channel a;
fix #275313: rework mixer ui 2 Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Setting color in collapsed mode now affects all channels. Using shared_ptr to track MixerTrackItem. Part changes now affect all instruments. Creating new track UI object to handle parts. Using shard_ptr to track MixerTrackItem objects. setting port and channel data now. Changing to horizontal layout. Fixing knob display. Chaning track control appearance. Setting init slider window size. Switchong back to vertical orientation. Fixing a few UI bugs in the slider. Tracks now left aligned. Moving details panel above mixer. Now changing track selection when user clicks on sliders. Pan and volume controls now reflect track color. Showing volume and pan values in tooltips. Creating a new slider control for mixer. Switching Channel's volume, pan, reverb and chorus and chaning them to doubles with a decimal range. No longer writing out vol, pan, chor, reverb when at default values. Nolonger writing vol, pan, chorus, reverb as controler values in output file. Now testing against default values on write. More export fixes. Manually editing test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. More test changes to make Travis happy. More test changes to make Travis happy. Importing MusicXML now matches new volume, pan ranges. Changing range of pan. Fixing a few bugs with calculating MIDI. Altering test files for Travis. fix #275313: rework-mixer-ui-2 Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Setting color in collapsed mode now affects all channels. Using shared_ptr to track MixerTrackItem. Part changes now affect all instruments. Creating new track UI object to handle parts. Using shard_ptr to track MixerTrackItem objects. setting port and channel data now. Changing to horizontal layout. Fixing knob display. Chaning track control appearance. Setting init slider window size. Switchong back to vertical orientation. Fixing a few UI bugs in the slider. Tracks now left aligned. Moving details panel above mixer. Now changing track selection when user clicks on sliders. Pan and volume controls now reflect track color. Showing volume and pan values in tooltips. Creating a new slider control for mixer. Switching Channel's volume, pan, reverb and chorus and chaning them to doubles with a decimal range. No longer writing out vol, pan, chor, reverb when at default values. Nolonger writing vol, pan, chorus, reverb as controler values in output file. Now testing against default values on write. More export fixes. Manually editing test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. More test changes to make Travis happy. More test changes to make Travis happy. Importing MusicXML now matches new volume, pan ranges. Changing range of pan. Fixing a few bugs with calculating MIDI. Altering test files for Travis. Restoring the volume, pan, chorus, reverb to original char data type & range. UI now shows different 'user friendly' ranges. Overwriting tests with versions from master. mtest/libmscore/compat114/clef_missing_first-ref.mscx mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml mtest/musicxml/io/testUninitializedDivisions_ref.xml Restoring test files to original state. Restoring test files to original state. Restoring old values for importing files. Restoring part methods. mtest/importmidi/simplify_8th_dotted_no_staccato.mscx mtest/libmscore/compat114/clef_missing_first-ref.mscx mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml mtest/musicxml/io/testUninitializedDivisions_ref.xml Rearranging UI components for better feel. Improving UI. Fixed crash when changing part name. Adding support for two lighting modes. Showing part name over channel expansion. Adding master gain control to mixer. Changing color of gain slider. Adapting to latest source in main. Changing master gain slider to use decibel calculation. CSS now set on tracks whenever a Paint event received. Restoring mixer slider values to refect MIDI ranges. Fixing crash when drumkit checked. Fixing crash when closing score. Fixing alignment in mixer details. Tweaking UI for better appearance.
2018-11-13 18:43:19 +01:00
a.setChorus(0);
a.setReverb(0);
a.setName(Channel::DEFAULT_NAME);
a.setBank(0);
a.setVolume(90);
a.setPan(0);
2012-06-07 09:24:05 +02:00
t->channel.append(a);
}
Part* part = new Part(this);
part->initFromInstrTemplate(t);
int n = nstaves();
for (int i = 0; i < t->nstaves(); ++i) {
2014-08-16 13:32:08 +02:00
Staff* staff = new Staff(this);
staff->setPart(part);
staff->setLines(Fraction(0,1), t->staffLines[i]);
staff->setSmall(Fraction(0,1), t->smallStaff[i]);
2012-06-07 09:24:05 +02:00
if (i == 0) {
2017-03-31 13:03:15 +02:00
staff->setBracketType(0, t->bracket[0]);
staff->setBracketSpan(0, t->nstaves());
2012-06-07 09:24:05 +02:00
}
2015-04-28 22:12:32 +02:00
undoInsertStaff(staff, i);
2012-06-07 09:24:05 +02:00
}
part->staves()->front()->setBarLineSpan(part->nstaves());
2014-08-16 14:18:06 +02:00
undoInsertPart(part, n);
2012-06-07 09:24:05 +02:00
fixTicks();
2016-03-11 12:18:46 +01:00
masterScore()->rebuildMidiMapping();
2012-06-07 09:24:05 +02:00
}
//---------------------------------------------------------
// appendMeasures
//---------------------------------------------------------
void Score::appendMeasures(int n)
{
for (int i = 0; i < n; ++i)
2017-01-18 14:16:33 +01:00
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)
{
2013-07-05 11:23:52 +02:00
_spanner.addSpanner(s);
2013-06-20 13:57:15 +02:00
}
//---------------------------------------------------------
// removeSpanner
//---------------------------------------------------------
void Score::removeSpanner(Spanner* s)
{
2013-07-05 11:23:52 +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 ?
//---------------------------------------------------------
bool Score::isSpannerStartEnd(const Fraction& tick, int track) const
2013-06-10 11:03:34 +02:00
{
2013-07-05 11:23:52 +02:00
for (auto i : _spanner.map()) {
2013-06-20 13:57:15 +02:00
if (i.second->track() != track)
2013-06-10 11:03:34 +02:00
continue;
2013-06-20 13:57:15 +02:00
if (i.second->tick() == tick || i.second->tick2() == tick)
2013-06-10 11:03:34 +02:00
return true;
}
return false;
}
void Score::insertTime(const Fraction& tick, const Fraction& len)
{
for (Staff* staff : staves())
staff->insertTime(tick, len);
for (Part* part : parts())
part->insertTime(tick, len);
2013-06-19 16:25:29 +02:00
}
//---------------------------------------------------------
// addUnmanagedSpanner
//---------------------------------------------------------
void Score::addUnmanagedSpanner(Spanner* s)
{
_unmanagedSpanner.insert(s);
}
//---------------------------------------------------------
// removeSpanner
//---------------------------------------------------------
void Score::removeUnmanagedSpanner(Spanner* s)
{
_unmanagedSpanner.erase(s);
}
//---------------------------------------------------------
2013-10-18 12:21:01 +02:00
// setPos
//---------------------------------------------------------
void MasterScore::setPos(POS pos, Fraction tick)
{
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
2014-11-17 16:44:05 +01:00
// 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()));
}
2014-05-22 16:18:35 +02:00
//---------------------------------------------------------
// uniqueStaves
//---------------------------------------------------------
QList<int> Score::uniqueStaves() const
{
QList<int> sl;
for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) {
Staff* s = staff(staffIdx);
2018-04-27 13:29:20 +02:00
if (s->links()) {
2014-05-22 16:18:35 +02:00
bool alreadyInList = false;
for (int idx : sl) {
2018-04-27 13:29:20 +02:00
if (s->links()->contains(staff(idx))) {
2014-05-22 16:18:35 +02:00
alreadyInList = true;
break;
}
}
if (alreadyInList)
continue;
}
sl.append(staffIdx);
}
return sl;
}
2014-05-29 12:13:49 +02:00
//---------------------------------------------------------
// findCR
// find chord/rest <= tick in track
//---------------------------------------------------------
ChordRest* Score::findCR(Fraction tick, int track) const
2014-05-29 12:13:49 +02:00
{
Measure* m = tick2measureMM(tick);
2014-07-27 16:48:05 +02:00
if (!m) {
qDebug("findCR: no measure for tick %d", tick.ticks());
2014-07-27 16:11:59 +02:00
return nullptr;
2014-07-27 16:48:05 +02:00
}
2014-05-29 12:13:49 +02:00
// attach to first rest all spanner when mmRest
if (m->isMMRest())
tick = m->tick();
2017-03-08 13:12:26 +01:00
Segment* s = m->first(SegmentType::ChordRest);
for (Segment* ns = s; ; ns = ns->next(SegmentType::ChordRest)) {
2014-05-29 12:13:49 +02:00
if (ns == 0 || ns->tick() > tick)
break;
Element* el = ns->element(track);
if (el && el->isRest() && toRest(el)->isGap())
continue;
else if (el)
2014-05-29 12:13:49 +02:00
s = ns;
}
Element* el = s->element(track);
if (el && el->isRest() && toRest(el)->isGap())
s = 0;
2014-05-29 12:13:49 +02:00
if (s)
2016-06-03 10:17:06 +02:00
return toChordRest(s->element(track));
2014-05-29 12:13:49 +02:00
return nullptr;
}
2014-08-18 11:33:17 +02:00
//---------------------------------------------------------
// findCRinStaff
// find last chord/rest on staff that ends before tick
2014-08-18 11:33:17 +02:00
//---------------------------------------------------------
ChordRest* Score::findCRinStaff(const Fraction& tick, int staffIdx) const
2014-08-18 11:33:17 +02:00
{
Fraction ptick = tick - Fraction::fromTicks(1);
Measure* m = tick2measureMM(ptick);
2014-08-18 11:33:17 +02:00
if (!m) {
qDebug("findCRinStaff: no measure for tick %d", ptick.ticks());
return 0;
2014-08-18 11:33:17 +02:00
}
// 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;
2014-08-18 11:33:17 +02:00
int actualTrack = strack;
Fraction lastTick = Fraction(-1,1);
2017-03-08 13:12:26 +01:00
for (Segment* ns = s; ; ns = ns->next(SegmentType::ChordRest)) {
if (ns == 0 || ns->tick() > ptick)
2014-08-18 11:33:17 +02:00
break;
// found a segment; now find longest cr on this staff that does not overlap tick
2014-08-18 11:33:17 +02:00
for (int t = strack; t < etrack; ++t) {
2016-06-03 10:17:06 +02:00
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;
}
2014-08-18 11:33:17 +02:00
}
}
}
if (s)
2016-06-03 10:17:06 +02:00
return toChordRest(s->element(actualTrack));
return 0;
2014-08-18 11:33:17 +02:00
}
//---------------------------------------------------------
// setSoloMute
// called once at opening file, adds soloMute marks
//---------------------------------------------------------
2016-03-11 12:18:46 +01:00
void MasterScore::setSoloMute()
{
for (unsigned i = 0; i < _midiMapping.size(); i++) {
Channel* b = _midiMapping[i].articulation();
fix #275313: rework mixer ui 2 Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Setting color in collapsed mode now affects all channels. Using shared_ptr to track MixerTrackItem. Part changes now affect all instruments. Creating new track UI object to handle parts. Using shard_ptr to track MixerTrackItem objects. setting port and channel data now. Changing to horizontal layout. Fixing knob display. Chaning track control appearance. Setting init slider window size. Switchong back to vertical orientation. Fixing a few UI bugs in the slider. Tracks now left aligned. Moving details panel above mixer. Now changing track selection when user clicks on sliders. Pan and volume controls now reflect track color. Showing volume and pan values in tooltips. Creating a new slider control for mixer. Switching Channel's volume, pan, reverb and chorus and chaning them to doubles with a decimal range. No longer writing out vol, pan, chor, reverb when at default values. Nolonger writing vol, pan, chorus, reverb as controler values in output file. Now testing against default values on write. More export fixes. Manually editing test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. More test changes to make Travis happy. More test changes to make Travis happy. Importing MusicXML now matches new volume, pan ranges. Changing range of pan. Fixing a few bugs with calculating MIDI. Altering test files for Travis. fix #275313: rework-mixer-ui-2 Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Setting color in collapsed mode now affects all channels. Using shared_ptr to track MixerTrackItem. Part changes now affect all instruments. Creating new track UI object to handle parts. Using shard_ptr to track MixerTrackItem objects. setting port and channel data now. Changing to horizontal layout. Fixing knob display. Chaning track control appearance. Setting init slider window size. Switchong back to vertical orientation. Fixing a few UI bugs in the slider. Tracks now left aligned. Moving details panel above mixer. Now changing track selection when user clicks on sliders. Pan and volume controls now reflect track color. Showing volume and pan values in tooltips. Creating a new slider control for mixer. Switching Channel's volume, pan, reverb and chorus and chaning them to doubles with a decimal range. No longer writing out vol, pan, chor, reverb when at default values. Nolonger writing vol, pan, chorus, reverb as controler values in output file. Now testing against default values on write. More export fixes. Manually editing test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. More test changes to make Travis happy. More test changes to make Travis happy. Importing MusicXML now matches new volume, pan ranges. Changing range of pan. Fixing a few bugs with calculating MIDI. Altering test files for Travis. Restoring the volume, pan, chorus, reverb to original char data type & range. UI now shows different 'user friendly' ranges. Overwriting tests with versions from master. mtest/libmscore/compat114/clef_missing_first-ref.mscx mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml mtest/musicxml/io/testUninitializedDivisions_ref.xml Restoring test files to original state. Restoring test files to original state. Restoring old values for importing files. Restoring part methods. mtest/importmidi/simplify_8th_dotted_no_staccato.mscx mtest/libmscore/compat114/clef_missing_first-ref.mscx mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml mtest/musicxml/io/testUninitializedDivisions_ref.xml Rearranging UI components for better feel. Improving UI. Fixed crash when changing part name. Adding support for two lighting modes. Showing part name over channel expansion. Adding master gain control to mixer. Changing color of gain slider. Adapting to latest source in main. Changing master gain slider to use decibel calculation. CSS now set on tracks whenever a Paint event received. Restoring mixer slider values to refect MIDI ranges. Fixing crash when drumkit checked. Fixing crash when closing score. Fixing alignment in mixer details. Tweaking UI for better appearance.
2018-11-13 18:43:19 +01:00
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();
fix #275313: rework mixer ui 2 Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Setting color in collapsed mode now affects all channels. Using shared_ptr to track MixerTrackItem. Part changes now affect all instruments. Creating new track UI object to handle parts. Using shard_ptr to track MixerTrackItem objects. setting port and channel data now. Changing to horizontal layout. Fixing knob display. Chaning track control appearance. Setting init slider window size. Switchong back to vertical orientation. Fixing a few UI bugs in the slider. Tracks now left aligned. Moving details panel above mixer. Now changing track selection when user clicks on sliders. Pan and volume controls now reflect track color. Showing volume and pan values in tooltips. Creating a new slider control for mixer. Switching Channel's volume, pan, reverb and chorus and chaning them to doubles with a decimal range. No longer writing out vol, pan, chor, reverb when at default values. Nolonger writing vol, pan, chorus, reverb as controler values in output file. Now testing against default values on write. More export fixes. Manually editing test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. More test changes to make Travis happy. More test changes to make Travis happy. Importing MusicXML now matches new volume, pan ranges. Changing range of pan. Fixing a few bugs with calculating MIDI. Altering test files for Travis. fix #275313: rework-mixer-ui-2 Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Setting color in collapsed mode now affects all channels. Using shared_ptr to track MixerTrackItem. Part changes now affect all instruments. Creating new track UI object to handle parts. Using shard_ptr to track MixerTrackItem objects. setting port and channel data now. Changing to horizontal layout. Fixing knob display. Chaning track control appearance. Setting init slider window size. Switchong back to vertical orientation. Fixing a few UI bugs in the slider. Tracks now left aligned. Moving details panel above mixer. Now changing track selection when user clicks on sliders. Pan and volume controls now reflect track color. Showing volume and pan values in tooltips. Creating a new slider control for mixer. Switching Channel's volume, pan, reverb and chorus and chaning them to doubles with a decimal range. No longer writing out vol, pan, chor, reverb when at default values. Nolonger writing vol, pan, chorus, reverb as controler values in output file. Now testing against default values on write. More export fixes. Manually editing test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. More test changes to make Travis happy. More test changes to make Travis happy. Importing MusicXML now matches new volume, pan ranges. Changing range of pan. Fixing a few bugs with calculating MIDI. Altering test files for Travis. Restoring the volume, pan, chorus, reverb to original char data type & range. UI now shows different 'user friendly' ranges. Overwriting tests with versions from master. mtest/libmscore/compat114/clef_missing_first-ref.mscx mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml mtest/musicxml/io/testUninitializedDivisions_ref.xml Restoring test files to original state. Restoring test files to original state. Restoring old values for importing files. Restoring part methods. mtest/importmidi/simplify_8th_dotted_no_staccato.mscx mtest/libmscore/compat114/clef_missing_first-ref.mscx mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml mtest/musicxml/io/testUninitializedDivisions_ref.xml Rearranging UI components for better feel. Improving UI. Fixed crash when changing part name. Adding support for two lighting modes. Showing part name over channel expansion. Adding master gain control to mixer. Changing color of gain slider. Adapting to latest source in main. Changing master gain slider to use decibel calculation. CSS now set on tracks whenever a Paint event received. Restoring mixer slider values to refect MIDI ranges. Fixing crash when drumkit checked. Fixing crash when closing score. Fixing alignment in mixer details. Tweaking UI for better appearance.
2018-11-13 18:43:19 +01:00
a->setSoloMute((i != j && !a->solo() && !sameMidiMapping));
a->setSolo(i == j || a->solo() || sameMidiMapping);
}
}
}
}
//---------------------------------------------------------
// setImportedFilePath
//---------------------------------------------------------
void Score::setImportedFilePath(const QString& filePath)
{
_importedFilePath = filePath;
}
//---------------------------------------------------------
// nmeasure
//---------------------------------------------------------
int Score::nmeasures() const
{
int n = 0;
for (const Measure* m = firstMeasure(); m; m = m->nextMeasure())
n++;
return n;
}
//---------------------------------------------------------
// hasLyrics
//---------------------------------------------------------
bool Score::hasLyrics()
{
2017-03-08 13:12:26 +01:00
SegmentType st = SegmentType::ChordRest;
for (Segment* seg = firstMeasure()->first(st); seg; seg = seg->next1(st)) {
for (int i = 0; i < ntracks(); ++i) {
2016-08-17 12:52:35 +02:00
ChordRest* cr = toChordRest(seg->element(i));
2016-08-24 14:49:34 +02:00
if (cr && !cr->lyrics().empty())
return true;
}
}
return false;
}
//---------------------------------------------------------
// hasHarmonies
//---------------------------------------------------------
bool Score::hasHarmonies()
{
2017-03-08 13:12:26 +01:00
SegmentType st = SegmentType::ChordRest;
for (Segment* seg = firstMeasure()->first(st); seg; seg = seg->next1(st)) {
for (Element* e : seg->annotations()) {
2017-01-18 14:16:33 +01:00
if (e->type() == ElementType::HARMONY)
return true;
}
}
return false;
}
//---------------------------------------------------------
// lyricCount
//---------------------------------------------------------
int Score::lyricCount()
{
size_t count = 0;
2017-03-08 13:12:26 +01:00
SegmentType st = SegmentType::ChordRest;
for (Segment* seg = firstMeasure()->first(st); seg; seg = seg->next1(st)) {
for (int i = 0; i < ntracks(); ++i) {
2016-08-17 12:52:35 +02:00
ChordRest* cr = toChordRest(seg->element(i));
if (cr)
count += cr->lyrics().size();
}
}
return int(count);
}
//---------------------------------------------------------
// harmonyCount
//---------------------------------------------------------
int Score::harmonyCount()
{
int count = 0;
2017-03-08 13:12:26 +01:00
SegmentType st = SegmentType::ChordRest;
for (Segment* seg = firstMeasure()->first(st); seg; seg = seg->next1(st)) {
for (Element* e : seg->annotations()) {
2017-01-18 14:16:33 +01:00
if (e->type() == ElementType::HARMONY)
count++;
}
}
return count;
}
2016-08-17 12:52:35 +02:00
//---------------------------------------------------------
// extractLyrics
//---------------------------------------------------------
QString Score::extractLyrics()
{
QString result;
masterScore()->setExpandRepeats(true);
2017-03-08 13:12:26 +01:00
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
2016-08-17 12:52:35 +02:00
ChordRest* cr = toChordRest(seg->element(track));
if (!cr || cr->lyrics().empty())
continue;
2016-08-17 12:52:35 +02:00
if (cr->lyrics().size() > maxLyrics)
maxLyrics = cr->lyrics().size();
2016-08-25 09:49:19 +02:00
if (playCount >= int(cr->lyrics().size()))
continue;
2018-01-16 13:38:17 +01:00
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
2016-08-25 09:49:19 +02:00
for (unsigned lyricsNumber = 0; lyricsNumber < maxLyrics; lyricsNumber++) {
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
2016-08-25 09:49:19 +02:00
unsigned playCount = m->playbackCount();
if (lyricsNumber >= playCount) {
for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
// consider voice 1 only
2016-08-17 12:52:35 +02:00
ChordRest* cr = toChordRest(seg->element(track));
if (!cr || cr->lyrics().empty())
continue;
2016-08-17 12:52:35 +02:00
if (cr->lyrics().size() > maxLyrics)
maxLyrics = cr->lyrics().size();
if (lyricsNumber >= cr->lyrics().size())
continue;
2018-01-16 13:38:17 +01:00
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();
}
//---------------------------------------------------------
// keysig
//---------------------------------------------------------
int Score::keysig()
{
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;
2018-03-27 15:36:00 +02:00
if (!styleB(Sid::concertPitch) && diff)
result = transposeKey(key, diff);
break;
}
return int(result);
}
//---------------------------------------------------------
// duration
//---------------------------------------------------------
int Score::duration()
{
masterScore()->setExpandRepeats(true);
const RepeatList& rl = repeatList();
if (rl.empty())
return 0;
const RepeatSegment* rs = rl.last();
return lrint(utick2utime(rs->utick + rs->len()));
}
//---------------------------------------------------------
// createRehearsalMarkText
//---------------------------------------------------------
QString Score::createRehearsalMarkText(RehearsalMark* current) const
{
Fraction tick = current->segment()->tick();
RehearsalMark* before = 0;
RehearsalMark* after = 0;
2017-03-08 13:12:26 +01:00
for (Segment* s = firstSegment(SegmentType::All); s; s = s->next1()) {
for (Element* e : s->annotations()) {
2017-01-18 14:16:33 +01:00
if (e && e->type() == ElementType::REHEARSAL_MARK) {
if (s->tick() < tick)
2016-06-03 10:17:06 +02:00
before = toRehearsalMark(e);
else if (s->tick() > tick) {
2016-06-03 10:17:06 +02:00
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;
2015-06-08 17:58:07 +02:00
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;
}
//---------------------------------------------------------
// 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
{
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);
}
}
}
//---------------------------------------------------------
// changeVoice
// moves selected notes into specified voice if possible
//---------------------------------------------------------
void Score::changeVoice(int voice)
{
startCmd();
QList<Element*> el;
2015-06-27 20:51:04 +02:00
QList<Element*> oel = selection().elements(); // make copy
for (Element* e : oel) {
2017-01-18 14:16:33 +01:00
if (e->type() == ElementType::NOTE) {
2016-06-03 10:17:06 +02:00
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;
2016-06-03 10:17:06 +02:00
ChordRest* dstCR = toChordRest(s->element(dstTrack));
Chord* dstChord = nullptr;
2016-07-31 15:23:11 +02:00
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
2016-06-03 10:17:06 +02:00
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;
2017-03-08 13:12:26 +01:00
for (Segment* s2 = m->first(SegmentType::ChordRest); s2; s2 = s2->next()) {
if (s2->segmentType() != SegmentType::ChordRest)
2015-06-27 20:51:04 +02:00
continue;
2016-06-03 10:17:06 +02:00
ChordRest* cr2 = toChordRest(s2->element(dstTrack));
2017-01-18 14:16:33 +01:00
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)
2017-03-08 13:12:26 +01:00
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());
}
}
}
}
}
2016-02-06 22:03:43 +01:00
if (!el.empty())
selection().clear();
2015-06-27 20:51:04 +02:00
for (Element* e : el)
select(e, SelectType::ADD, -1);
2016-03-02 13:20:19 +01:00
setLayoutAll();
endCmd();
}
#if 0
//---------------------------------------------------------
// cropPage - crop a single page score to the content
/// margins will be applied on the 4 sides
//---------------------------------------------------------
void Score::cropPage(qreal margins)
{
if (npages() == 1) {
Page* page = pages()[0];
if (page) {
QRectF ttbox = page->tbbox();
qreal margin = margins / INCH;
2015-11-16 14:24:47 +01:00
f.setSize(QSizeF((ttbox.width() / DPI) + 2 * margin, (ttbox.height()/ DPI) + 2 * margin));
2015-11-16 14:24:47 +01:00
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());
}
}
}
#endif
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant Score::getProperty(Pid /*id*/) const
{
qDebug("Score::getProperty: unhandled id");
return QVariant();
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool Score::setProperty(Pid /*id*/, const QVariant& /*v*/)
{
qDebug("Score::setProperty: unhandled id");
2016-05-18 15:43:47 +02:00
setLayoutAll();
return true;
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant Score::propertyDefault(Pid /*id*/) const
{
return QVariant();
}
2016-03-02 13:20:19 +01:00
//---------------------------------------------------------
// setStyle
//---------------------------------------------------------
void Score::setStyle(const MStyle& s)
{
2017-01-05 11:23:47 +01:00
style() = s;
2016-03-02 13:20:19 +01:00
}
2016-03-10 10:41:31 +01:00
//---------------------------------------------------------
// getTextStyleUserName
//---------------------------------------------------------
QString Score::getTextStyleUserName(Tid tid)
{
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;
}
2016-03-10 10:41:31 +01:00
//---------------------------------------------------------
// MasterScore
//---------------------------------------------------------
MasterScore::MasterScore()
: Score()
{
_tempomap = new TempoMap;
_sigmap = new TimeSigMap();
_repeatList = new RepeatList(this);
_revisions = new Revisions;
setMasterScore(this);
_pos[int(POS::CURRENT)] = Fraction(0,1);
_pos[int(POS::LEFT)] = Fraction(0,1);
_pos[int(POS::RIGHT)] = Fraction(0,1);
2016-03-10 10:41:31 +01:00
#if defined(Q_OS_WIN)
metaTags().insert("platform", "Microsoft Windows");
#elif defined(Q_OS_MAC)
metaTags().insert("platform", "Apple Macintosh");
#elif defined(Q_OS_LINUX)
metaTags().insert("platform", "Linux");
#else
metaTags().insert("platform", "Unknown");
#endif
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));
}
2017-01-05 11:23:47 +01:00
MasterScore::MasterScore(const MStyle& s)
2016-03-10 10:41:31 +01:00
: MasterScore{}
{
2017-01-05 11:23:47 +01:00
_movements = new Movements;
_movements->push_back(this);
setStyle(s);
2016-03-10 10:41:31 +01:00
}
MasterScore::~MasterScore()
{
delete _revisions;
delete _repeatList;
delete _sigmap;
delete _tempomap;
qDeleteAll(_excerpts);
}
2017-01-31 12:21:44 +01:00
//---------------------------------------------------------
// setMovements
//---------------------------------------------------------
void MasterScore::setMovements(Movements* m)
{
_movements = m;
if (_movements)
_movements->push_back(this);
}
2016-03-10 10:41:31 +01:00
//---------------------------------------------------------
// isSavable
//---------------------------------------------------------
bool MasterScore::isSavable() const
{
// TODO: check if file can be created if it does not exist
2016-03-11 12:18:46 +01:00
return fileInfo()->isWritable() || !fileInfo()->exists();
2016-03-10 10:41:31 +01:00
}
//---------------------------------------------------------
// setTempomap
//---------------------------------------------------------
void MasterScore::setTempomap(TempoMap* tm)
{
delete _tempomap;
_tempomap = tm;
}
2016-10-10 18:37:28 +02:00
//---------------------------------------------------------
// removeOmr
//---------------------------------------------------------
void MasterScore::removeOmr()
{
_showOmr = false;
#ifdef OMR
delete _omr;
#endif
_omr = 0;
}
2016-03-10 10:41:31 +01:00
//---------------------------------------------------------
// setName
//---------------------------------------------------------
2016-10-10 18:37:28 +02:00
void MasterScore::setName(const QString& ss)
2016-03-10 10:41:31 +01:00
{
QString s(ss);
s.replace('/', '_'); // for sanity
if (!(s.endsWith(".mscz") || s.endsWith(".mscx")))
s += ".mscz";
info.setFile(s);
}
//---------------------------------------------------------
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
2016-03-10 10:41:31 +01:00
{
2016-10-10 18:37:28 +02:00
return fileInfo()->completeBaseName();
}
QString Score::title() const
{
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)
{
_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
{
int idx = 0;
for (Part* p : _parts) {
if (p == part)
break;
idx += p->nstaves();
}
return idx;
}
2017-01-05 11:23:47 +01:00
//---------------------------------------------------------
// setUpdateAll
//---------------------------------------------------------
void MasterScore::setUpdateAll()
{
_cmdState.setUpdateMode(UpdateMode::UpdateAll);
}
//---------------------------------------------------------
// setLayoutAll
//---------------------------------------------------------
2019-10-24 15:49:23 +02:00
void MasterScore::setLayoutAll(int staff, const Element* e)
2017-01-05 11:23:47 +01:00
{
_cmdState.setTick(Fraction(0,1));
_cmdState.setTick(measures()->last() ? measures()->last()->endTick() : Fraction(0,1));
2019-10-24 15:49:23 +02:00
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);
2019-10-24 15:49:23 +02:00
_cmdState.setElement(e);
}
2019-10-24 15:49:23 +02:00
}
//---------------------------------------------------------
// setLayout
//---------------------------------------------------------
void MasterScore::setLayout(const Fraction& t, int staff, const Element* e)
{
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)
{
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);
2019-10-24 15:49:23 +02:00
_cmdState.setElement(e);
}
2017-01-05 11:23:47 +01:00
}
//---------------------------------------------------------
// setPlaybackScore
//---------------------------------------------------------
void MasterScore::setPlaybackScore(Score* score)
{
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);
}
}
}
}
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.
//---------------------------------------------------------
void MasterScore::updateExpressive(Synthesizer* synth)
2018-12-22 11:43:23 +01:00
{
SynthesizerState s = synthesizerState();
SynthesizerGroup g = s.group("master");
int method = 1;
2018-12-22 11:43:23 +01:00
for (IdValue idVal : g) {
if (idVal.id == 4) {
method = idVal.data.toInt();
break;
}
}
updateExpressive(synth, (method != 0));
2018-12-22 11:43:23 +01:00
}
void MasterScore::updateExpressive(Synthesizer* synth, bool expressive, bool force /* = false */)
2018-12-22 11:43:23 +01:00
{
if (!synth)
2018-12-22 11:43:23 +01:00
return;
if (!force) {
SynthesizerState s = synthesizerState();
SynthesizerGroup g = s.group("master");
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}
2018-12-22 11:43:23 +01:00
}
}
}
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);
2018-12-22 11:43:23 +01:00
}
}
}
//---------------------------------------------------------
// rebuildAndUpdateExpressive
// implicitly rebuild midi mappings as well. Should be preferred over
// just updateExpressive, in most cases.
//---------------------------------------------------------
void MasterScore::rebuildAndUpdateExpressive(Synthesizer* synth)
{
// Rebuild midi mappings to make sure we have playback channels
rebuildMidiMapping();
updateExpressive(synth);
// 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
{
return !(isMaster() && static_cast<const MasterScore*>(this)->prev());
}
//---------------------------------------------------------
// Movements
//---------------------------------------------------------
Movements::Movements()
2017-01-31 12:21:44 +01:00
: std::vector<MasterScore*>()
2017-01-05 11:23:47 +01:00
{
_undo = new UndoStack();
}
Movements::~Movements()
{
qDeleteAll(_pages);
delete _undo;
}
//---------------------------------------------------------
// ScoreLoad::_loading
// If the _loading > 0 then pushes and pops to
// the undo stack do not emit a warning.
// Usually pushes and pops to the undo stack are only
// valid inside a startCmd() - endCmd(). Exceptions
// occurred during score loading.
//---------------------------------------------------------
int ScoreLoad::_loading = 0;
2013-05-13 18:49:17 +02:00
}