MuseScore/src/notation/internal/masternotation.cpp
2022-01-14 18:14:20 +02:00

646 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "masternotation.h"
#include <QFileInfo>
#include "log.h"
#include "translation.h"
#include "libmscore/factory.h"
#include "libmscore/masterscore.h"
#include "libmscore/part.h"
#include "libmscore/staff.h"
#include "libmscore/excerpt.h"
#include "libmscore/measure.h"
#include "libmscore/box.h"
#include "libmscore/keysig.h"
#include "libmscore/rest.h"
#include "libmscore/tempotext.h"
#include "libmscore/undo.h"
#include "excerptnotation.h"
#include "masternotationparts.h"
#include "masternotationmididata.h"
#include "../notationerrors.h"
using namespace mu::notation;
using namespace mu::async;
static ExcerptNotation* get_impl(const IExcerptNotationPtr& excerpt)
{
return static_cast<ExcerptNotation*>(excerpt.get());
}
MasterNotation::MasterNotation()
: Notation()
{
m_parts = std::make_shared<MasterNotationParts>(this, interaction(), undoStack());
m_notationMidiData = std::make_shared<MasterNotationMidiData>(this, m_notationChanged);
m_parts->partsChanged().onNotify(this, [this]() {
notifyAboutNotationChanged();
});
undoStack()->stackChanged().onNotify(this, [this]() {
notifyAboutNeedSaveChanged();
});
}
MasterNotation::~MasterNotation()
{
m_parts = nullptr;
}
INotationPtr MasterNotation::notation()
{
return shared_from_this();
}
void MasterNotation::setMasterScore(Ms::MasterScore* score)
{
if (masterScore() == score) {
return;
}
TRACEFUNC;
setScore(score);
score->setSystemObjectStaves();
initExcerptNotations(masterScore()->excerpts());
m_notationMidiData->init(m_parts);
}
Ms::MasterScore* MasterNotation::masterScore() const
{
return dynamic_cast<Ms::MasterScore*>(score());
}
static void clearMeasures(Ms::Score* score)
{
for (Ms::Score* _score : score->scoreList()) {
Ms::MeasureBaseList* measures = _score->measures();
for (Ms::MeasureBase* measure = measures->first(); measure; measure = measure->next()) {
measure->deleteLater();
}
measures->clear();
}
}
static void createMeasures(Ms::Score* score, const ScoreCreateOptions& scoreOptions)
{
TRACEFUNC;
Ms::Fraction timesig(scoreOptions.timesigNumerator, scoreOptions.timesigDenominator);
score->sigmap()->add(0, timesig);
bool pickupMeasure = scoreOptions.withPickupMeasure;
int measures = scoreOptions.measures;
if (pickupMeasure) {
measures += 1;
}
Ms::Fraction firstMeasureTicks = pickupMeasure ? Ms::Fraction(scoreOptions.measureTimesigNumerator,
scoreOptions.measureTimesigDenominator) : timesig;
Ms::KeySigEvent ks;
ks.setKey(scoreOptions.key);
for (int i = 0; i < measures; ++i) {
Ms::Fraction tick = firstMeasureTicks + timesig * (i - 1);
if (i == 0) {
tick = Ms::Fraction(0, 1);
}
QList<Ms::Rest*> puRests;
for (Ms::Score* _score : score->scoreList()) {
Ms::Rest* rest = 0;
Ms::Measure* measure = mu::engraving::Factory::createMeasure(_score->dummy()->system());
measure->setTimesig(timesig);
measure->setTicks(timesig);
measure->setTick(tick);
if (pickupMeasure && tick.isZero()) {
measure->setIrregular(true); // dont count pickup measure
measure->setTicks(Ms::Fraction(scoreOptions.measureTimesigNumerator,
scoreOptions.measureTimesigDenominator));
}
_score->measures()->add(measure);
for (Ms::Staff* staff : _score->staves()) {
int staffIdx = staff->idx();
if (tick.isZero()) {
Ms::Measure* m = _score->firstMeasure();
Ms::Segment* s = m->getSegment(Ms::SegmentType::TimeSig, Ms::Fraction(0, 1));
Ms::TimeSig* ts = mu::engraving::Factory::createTimeSig(s);
ts->setTrack(staffIdx * Ms::VOICES);
ts->setSig(timesig, scoreOptions.timesigType);
s->add(ts);
Part* part = staff->part();
if (!part->instrument()->useDrumset()) {
//
// transpose key
//
Ms::KeySigEvent nKey = ks;
if (!nKey.custom() && !nKey.isAtonal() && part->instrument()->transpose().chromatic
&& !score->styleB(Ms::Sid::concertPitch)) {
int diff = -part->instrument()->transpose().chromatic;
nKey.setKey(Ms::transposeKey(nKey.key(), diff, part->preferSharpFlat()));
}
// do not create empty keysig unless custom or atonal
if (nKey.custom() || nKey.isAtonal() || nKey.key() != Key::C) {
staff->setKey(Ms::Fraction(0, 1), nKey);
Ms::Segment* ss = measure->getSegment(Ms::SegmentType::KeySig, Ms::Fraction(0, 1));
Ms::KeySig* keysig = mu::engraving::Factory::createKeySig(ss);
keysig->setTrack(staffIdx * Ms::VOICES);
keysig->setKeySigEvent(nKey);
ss->add(keysig);
}
}
}
// determined if this staff is linked to previous so we can reuse rests
bool linkedToPrevious = staffIdx && staff->isLinked(_score->staff(staffIdx - 1));
if (measure->timesig() != measure->ticks()) {
if (!linkedToPrevious) {
puRests.clear();
}
std::vector<Ms::TDuration> dList = Ms::toDurationList(measure->ticks(), false);
if (!dList.empty()) {
Ms::Fraction ltick = tick;
int k = 0;
foreach (Ms::TDuration d, dList) {
Ms::Segment* seg = measure->getSegment(Ms::SegmentType::ChordRest, ltick);
if (k < puRests.count()) {
rest = static_cast<Ms::Rest*>(puRests[k]->linkedClone());
} else {
rest = mu::engraving::Factory::createRest(seg, d);
puRests.append(rest);
}
rest->setScore(_score);
rest->setTicks(d.fraction());
rest->setTrack(staffIdx * Ms::VOICES);
seg->add(rest);
ltick += rest->actualTicks();
k++;
}
}
} else {
Ms::Segment* seg = measure->getSegment(Ms::SegmentType::ChordRest, tick);
if (linkedToPrevious && rest) {
rest = static_cast<Ms::Rest*>(rest->linkedClone());
} else {
rest = mu::engraving::Factory::createRest(seg, Ms::TDuration(Ms::DurationType::V_MEASURE));
}
rest->setScore(_score);
rest->setTicks(measure->ticks());
rest->setTrack(staffIdx * Ms::VOICES);
seg->add(rest);
}
}
}
}
}
//! NOTE: this method with all of its dependencies was copied from MU3
//! source: file.cpp, MuseScore::getNewFile()
mu::Ret MasterNotation::setupNewScore(Ms::MasterScore* score, const ScoreCreateOptions& scoreOptions)
{
TRACEFUNC;
setScore(score);
parts()->setParts(scoreOptions.parts, scoreOptions.order);
score->checkChordList();
applyOptions(score, scoreOptions);
{
Ms::ScoreLoad sl;
score->doLayout();
}
initExcerptNotations(score->excerpts());
addExcerptsToMasterScore(score->excerpts());
score->setExcerptsChanged(true);
m_notationMidiData->init(m_parts);
return make_ret(Err::NoError);
}
void MasterNotation::applyOptions(Ms::MasterScore* score, const ScoreCreateOptions& scoreOptions, bool createdFromTemplate)
{
TRACEFUNC;
Ms::VBox* nvb = nullptr;
if (createdFromTemplate) {
clearMeasures(score);
Ms::MeasureBase* mb = score->first();
if (mb && mb->isVBox()) {
Ms::VBox* tvb = toVBox(mb);
nvb = new Ms::VBox(score->dummy()->system());
nvb->setBoxHeight(tvb->boxHeight());
nvb->setBoxWidth(tvb->boxWidth());
nvb->setTopGap(tvb->topGap());
nvb->setBottomGap(tvb->bottomGap());
nvb->setTopMargin(tvb->topMargin());
nvb->setBottomMargin(tvb->bottomMargin());
nvb->setLeftMargin(tvb->leftMargin());
nvb->setRightMargin(tvb->rightMargin());
nvb->setAutoSizeEnabled(tvb->isAutoSizeEnabled());
}
score->setSystemObjectStaves(); // use the template to determine where system objects go
}
score->setName(qtrc("notation", "Untitled"));
score->setSaved(true);
score->setCreated(true);
score->checkChordList();
createMeasures(score, scoreOptions);
//
// select first rest
//
Measure* m = score->firstMeasure();
for (Ms::Segment* s = m->first(); s; s = s->next()) {
if (s->segmentType() == Ms::SegmentType::ChordRest) {
if (s->element(0)) {
score->select(s->element(0), SelectType::SINGLE, 0);
break;
}
}
}
{
QString title = score->metaTag("workTitle");
QString subtitle = score->metaTag("subtitle");
QString composer = score->metaTag("composer");
QString lyricist = score->metaTag("lyricist");
if (!title.isEmpty() || !subtitle.isEmpty() || !composer.isEmpty() || !lyricist.isEmpty()) {
Ms::MeasureBase* measure = score->measures()->first();
if (measure->type() != ElementType::VBOX) {
Ms::MeasureBase* nm = nvb ? nvb : new Ms::VBox(score->dummy()->system());
nm->setTick(Ms::Fraction(0, 1));
nm->setNext(measure);
score->measures()->add(nm);
measure = nm;
} else if (nvb) {
delete nvb;
}
auto setText = [score](Ms::TextStyleType textItemId, const QString& text) {
Ms::TextBase* textItem = score->getText(textItemId);
if (!textItem) {
textItem = score->addText(textItemId);
}
if (textItem) {
textItem->setPlainText(text);
}
};
if (!title.isEmpty()) {
setText(Ms::TextStyleType::TITLE, title);
}
if (!subtitle.isEmpty()) {
setText(Ms::TextStyleType::SUBTITLE, subtitle);
}
if (!composer.isEmpty()) {
setText(Ms::TextStyleType::COMPOSER, composer);
}
if (!lyricist.isEmpty()) {
setText(Ms::TextStyleType::POET, lyricist);
}
} else if (nvb) {
delete nvb;
}
}
if (scoreOptions.withTempo) {
Ms::Fraction ts(scoreOptions.timesigNumerator, scoreOptions.timesigDenominator);
QString text("<sym>metNoteQuarterUp</sym> = %1");
double bpm = scoreOptions.tempo.valueBpm;
switch (ts.denominator()) {
case 1:
bpm /= 4;
break;
case 2:
bpm /= 2;
break;
case 4:
break;
case 8:
if (ts.numerator() % 3 == 0) {
bpm /= 1.5;
} else {
bpm *= 2;
}
break;
case 16:
if (ts.numerator() % 3 == 0) {
bpm *= 1.5;
} else {
bpm *= 4;
}
break;
case 32:
if (ts.numerator() % 3 == 0) {
bpm *= 3;
} else {
bpm *= 8;
}
break;
case 64:
if (ts.numerator() % 3 == 0) {
bpm *= 6;
} else {
bpm *= 16;
}
break;
case 128:
if (ts.numerator() % 3 == 0) {
bpm *= 6;
} else {
bpm *= 16;
}
break;
default:
break;
}
bool withDot = scoreOptions.tempo.withDot;
switch (scoreOptions.tempo.duration) {
case DurationType::V_WHOLE:
text = "<sym>metNoteWhole</sym> = %1";
break;
case DurationType::V_HALF:
if (withDot) {
text = "<sym>metNoteHalfUp</sym><sym>space</sym><sym>metAugmentationDot</sym> = %1";
} else {
text = "<sym>metNoteHalfUp</sym> = %1";
}
break;
case DurationType::V_QUARTER:
if (withDot) {
text = "<sym>metNoteQuarterUp</sym><sym>space</sym><sym>metAugmentationDot</sym> = %1";
} else {
text = "<sym>metNoteQuarterUp</sym> = %1";
}
break;
case DurationType::V_EIGHTH:
if (withDot) {
text = "<sym>metNote8thUp</sym><sym>space</sym><sym>metAugmentationDot</sym> = %1";
} else {
text = "<sym>metNote8thUp</sym> = %1";
}
break;
case DurationType::V_16TH:
if (withDot) {
text = "<sym>metNote16thUp</sym><sym>space</sym><sym>metAugmentationDot</sym> = %1";
} else {
text = "<sym>metNote16thUp</sym> = %1";
}
break;
default:
break;
}
Ms::Segment* seg = score->firstMeasure()->first(Ms::SegmentType::ChordRest);
Ms::TempoText* tt = new Ms::TempoText(seg);
tt->setXmlText(text.arg(bpm));
double tempo = scoreOptions.tempo.valueBpm;
tempo /= 60; // bpm -> bps
tt->setTempo(tempo);
tt->setFollowText(true);
tt->setTrack(0);
seg->add(tt);
score->setTempo(seg, tempo);
}
}
mu::RetVal<bool> MasterNotation::created() const
{
RetVal<bool> result;
if (!score()) {
result.ret = make_ret(Err::NoScore);
return result;
}
return RetVal<bool>::make_ok(score()->created());
}
mu::ValNt<bool> MasterNotation::needSave() const
{
ValNt<bool> needSave;
needSave.val = !masterScore()->saved();
needSave.notification = m_needSaveNotification;
return needSave;
}
void MasterNotation::addExcerpts(const ExcerptNotationList& excerpts)
{
if (excerpts.empty()) {
return;
}
TRACEFUNC;
undoStack()->prepareChanges();
ExcerptNotationList result = m_excerpts.val;
for (IExcerptNotationPtr excerptNotation : excerpts) {
auto it = std::find(result.cbegin(), result.cend(), excerptNotation);
if (it != result.end()) {
continue;
}
ExcerptNotation* excerptNotationImpl = get_impl(excerptNotation);
if (!excerptNotationImpl->isCreated()) {
masterScore()->initAndAddExcerpt(excerptNotationImpl->excerpt(), false);
excerptNotationImpl->setIsCreated(true);
}
result.push_back(excerptNotation);
}
undoStack()->commitChanges();
doSetExcerpts(result);
}
void MasterNotation::removeExcerpts(const ExcerptNotationList& excerpts)
{
if (excerpts.empty()) {
return;
}
TRACEFUNC;
undoStack()->prepareChanges();
for (IExcerptNotationPtr excerptNotation : excerpts) {
auto it = std::find(m_excerpts.val.begin(), m_excerpts.val.end(), excerptNotation);
if (it == m_excerpts.val.end()) {
continue;
}
Ms::Excerpt* excerpt = get_impl(excerptNotation)->excerpt();
masterScore()->undo(new Ms::RemoveExcerpt(excerpt));
m_excerpts.val.erase(it);
}
undoStack()->commitChanges();
doSetExcerpts(m_excerpts.val);
}
void MasterNotation::doSetExcerpts(ExcerptNotationList excerpts)
{
TRACEFUNC;
m_excerpts.set(excerpts);
static_cast<MasterNotationParts*>(m_parts.get())->setExcerpts(excerpts);
for (auto excerpt : excerpts) {
excerpt->notation()->undoStack()->stackChanged().onNotify(this, [this]() {
notifyAboutNeedSaveChanged();
});
}
}
void MasterNotation::notifyAboutNeedSaveChanged()
{
m_needSaveNotification.notify();
}
IExcerptNotationPtr MasterNotation::newExcerptBlankNotation() const
{
auto excerptNotation = std::make_shared<ExcerptNotation>(new Ms::Excerpt(masterScore()));
excerptNotation->setTitle(qtrc("notation", "Part"));
excerptNotation->setIsCreated(false);
return excerptNotation;
}
mu::ValCh<ExcerptNotationList> MasterNotation::excerpts() const
{
return m_excerpts;
}
INotationPartsPtr MasterNotation::parts() const
{
return m_parts;
}
IMasterNotationMidiDataPtr MasterNotation::midiData() const
{
return m_notationMidiData;
}
ExcerptNotationList MasterNotation::potentialExcerpts() const
{
TRACEFUNC;
auto excerptExists = [this](const ID& partId) {
for (const Ms::Excerpt* excerpt : masterScore()->excerpts()) {
const QList<Part*>& excerptParts = excerpt->parts();
if (excerptParts.size() != 1) {
continue;
}
if (ID(excerptParts.first()->id()) == partId) {
return true;
}
}
return false;
};
QList<Part*> parts;
for (Part* part : score()->parts()) {
if (!excerptExists(part->id())) {
parts << part;
}
}
QList<Ms::Excerpt*> excerpts = Ms::Excerpt::createExcerptsFromParts(parts);
ExcerptNotationList result;
for (Ms::Excerpt* excerpt : excerpts) {
auto excerptNotation = std::make_shared<ExcerptNotation>(excerpt);
excerptNotation->setIsCreated(false);
result.push_back(excerptNotation);
}
return result;
}
void MasterNotation::onSaveCopy()
{
score()->setCreated(false);
undoStack()->stackChanged().notify();
}
void MasterNotation::initExcerptNotations(const QList<Ms::Excerpt*>& excerpts)
{
TRACEFUNC;
ExcerptNotationList notationExcerpts;
for (Ms::Excerpt* excerpt : excerpts) {
if (excerpt->isEmpty()) {
masterScore()->initEmptyExcerpt(excerpt);
}
auto excerptNotation = std::make_shared<ExcerptNotation>(excerpt);
excerptNotation->setIsCreated(true);
excerptNotation->notation()->setOpened(true);
notationExcerpts.push_back(excerptNotation);
}
doSetExcerpts(notationExcerpts);
}
void MasterNotation::addExcerptsToMasterScore(const QList<Ms::Excerpt*>& excerpts)
{
TRACEFUNC;
for (Ms::Excerpt* excerpt : excerpts) {
masterScore()->initAndAddExcerpt(excerpt, false);
}
}