MuseScore/src/libmscore/part.cpp
2021-04-19 17:08:37 +02:00

749 lines
20 KiB
C++

/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "part.h"
#include "staff.h"
#include "xml.h"
#include "score.h"
#include "style.h"
#include "note.h"
#include "drumset.h"
#include "instrtemplate.h"
#include "text.h"
#include "measure.h"
#include "stringdata.h"
#include "stafftype.h"
#include "sym.h"
#include "chordrest.h"
#include "fret.h"
namespace Ms {
//---------------------------------------------------------
// Part
//---------------------------------------------------------
Part::Part(Score* s)
: ScoreElement(s)
{
static std::atomic_int currentId { 0 };
_id = QString::number(++currentId);
_color = DEFAULT_COLOR;
_show = true;
_soloist = false;
_instruments.setInstrument(new Instrument, -1); // default instrument
_preferSharpFlat = PreferSharpFlat::DEFAULT;
}
//---------------------------------------------------------
// initFromInstrTemplate
//---------------------------------------------------------
void Part::initFromInstrTemplate(const InstrumentTemplate* t)
{
_partName = t->trackName;
setInstrument(Instrument::fromTemplate(t));
}
//---------------------------------------------------------
// staff
//---------------------------------------------------------
Staff* Part::staff(int idx) const
{
return _staves[idx];
}
//---------------------------------------------------------
// Part::masterPart
//---------------------------------------------------------
const Part* Part::masterPart() const
{
if (score()->isMaster()) {
return this;
}
if (_staves.empty()) {
return this;
}
Staff* st = _staves[0];
LinkedElements* links = st->links();
if (!links) {
return this;
}
for (ScoreElement* le : *links) {
if (le->isStaff() && toStaff(le)->score()->isMaster()) {
if (Part* p = toStaff(le)->part()) {
return p;
}
}
}
return this;
}
//---------------------------------------------------------
// Part::masterPart
//---------------------------------------------------------
Part* Part::masterPart()
{
return const_cast<Part*>(const_cast<const Part*>(this)->masterPart());
}
//---------------------------------------------------------
// readProperties
//---------------------------------------------------------
bool Part::readProperties(XmlReader& e)
{
const QStringRef& tag(e.name());
if (tag == "Staff") {
Staff* staff = new Staff(score());
staff->setPart(this);
score()->staves().push_back(staff);
_staves.push_back(staff);
staff->read(e);
} else if (tag == "Instrument") {
Instrument* instr = new Instrument;
instr->read(e, this);
setInstrument(instr, Fraction(-1, 1));
} else if (tag == "name") {
instrument()->setLongName(e.readElementText());
} else if (tag == "color") {
_color = e.readInt();
} else if (tag == "shortName") {
instrument()->setShortName(e.readElementText());
} else if (tag == "trackName") {
_partName = e.readElementText();
} else if (tag == "show") {
_show = e.readInt();
} else if (tag == "soloist") {
_soloist = e.readInt();
} else if (tag == "preferSharpFlat") {
_preferSharpFlat
=e.readElementText() == "sharps" ? PreferSharpFlat::SHARPS : PreferSharpFlat::FLATS;
} else {
return false;
}
return true;
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void Part::read(XmlReader& e)
{
while (e.readNextStartElement()) {
if (!readProperties(e)) {
e.unknown();
}
}
if (_partName.isEmpty()) {
_partName = instrument()->trackName();
}
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void Part::write(XmlWriter& xml) const
{
xml.stag(this);
for (const Staff* staff : _staves) {
staff->write(xml);
}
if (!_show) {
xml.tag("show", _show);
}
if (_soloist) {
xml.tag("soloist", _soloist);
}
xml.tag("trackName", _partName);
if (_color != DEFAULT_COLOR) {
xml.tag("color", _color);
}
if (_preferSharpFlat != PreferSharpFlat::DEFAULT) {
xml.tag("preferSharpFlat",
_preferSharpFlat == PreferSharpFlat::SHARPS ? "sharps" : "flats");
}
instrument()->write(xml, this);
xml.etag();
}
//---------------------------------------------------------
// setLongNames
//---------------------------------------------------------
void Part::setLongNames(QList<StaffName>& name, const Fraction& tick)
{
instrument(tick)->longNames() = name;
}
void Part::setShortNames(QList<StaffName>& name, const Fraction& tick)
{
instrument(tick)->shortNames() = name;
}
//---------------------------------------------------------
// setStaves
//---------------------------------------------------------
void Part::setStaves(int n)
{
int ns = _staves.size();
if (n < ns) {
qDebug("Part::setStaves(): remove staves not implemented!");
return;
}
int staffIdx = score()->staffIdx(this) + ns;
for (int i = ns; i < n; ++i) {
Staff* staff = new Staff(score());
staff->setPart(this);
_staves.push_back(staff);
score()->staves().insert(staffIdx, staff);
for (Measure* m = score()->firstMeasure(); m; m = m->nextMeasure()) {
m->insertStaff(staff, staffIdx);
if (m->hasMMRest()) {
m->mmRest()->insertStaff(staff, staffIdx);
}
}
++staffIdx;
}
}
//---------------------------------------------------------
// insertStaff
//---------------------------------------------------------
void Part::insertStaff(Staff* staff, int idx)
{
if (idx < 0 || idx > _staves.size()) {
idx = _staves.size();
}
_staves.insert(idx, staff);
staff->setPart(this);
}
//---------------------------------------------------------
// removeStaff
//---------------------------------------------------------
void Part::removeStaff(Staff* staff)
{
if (!_staves.removeOne(staff)) {
qDebug("Part::removeStaff: not found %p", staff);
return;
}
}
//---------------------------------------------------------
// setMidiProgram
// TODO
//---------------------------------------------------------
void Part::setMidiProgram(int program, int bank)
{
Channel* c = instrument()->channel(0);
c->setProgram(program);
c->setBank(bank);
// instrument()->setChannel(0, c);
}
//---------------------------------------------------------
// midiProgram
//---------------------------------------------------------
int Part::midiProgram() const
{
return instrument()->playbackChannel(0, masterScore())->program();
}
//---------------------------------------------------------
// midiChannel
//---------------------------------------------------------
int Part::midiChannel() const
{
return masterScore()->midiChannel(instrument()->channel(0)->channel());
}
//---------------------------------------------------------
// midiPort
//---------------------------------------------------------
int Part::midiPort() const
{
return masterScore()->midiPort(instrument()->channel(0)->channel());
}
//---------------------------------------------------------
// setMidiChannel
// Called from importmusicxml, importMidi and importGtp*.
// Specify tick to set MIDI channel to an InstrumentChange element.
// Usage:
// setMidiChannel(channel) to set channel
// setMidiChannel(-1, port) to set port
// setMidiChannel(channel, port) to set both
//---------------------------------------------------------
void Part::setMidiChannel(int ch, int port, const Fraction& tick)
{
Channel* channel = instrument(tick)->channel(0);
if (channel->channel() == -1) {
masterScore()->addMidiMapping(channel, this, port, ch);
} else {
masterScore()->updateMidiMapping(channel, this, port, ch);
}
}
//---------------------------------------------------------
// setInstrument
//---------------------------------------------------------
void Part::setInstrument(Instrument* i, Fraction tick)
{
_instruments.setInstrument(i, tick.ticks());
}
void Part::setInstrument(const Instrument&& i, Fraction tick)
{
_instruments.setInstrument(new Instrument(i), tick.ticks());
}
void Part::setInstrument(const Instrument& i, Fraction tick)
{
_instruments.setInstrument(new Instrument(i), tick.ticks());
}
void Part::setInstruments(const InstrumentList& instruments)
{
_instruments.clear();
for (auto it = instruments.begin(); it != instruments.end(); ++it) {
_instruments.setInstrument(it->second, it->first);
}
}
//---------------------------------------------------------
// removeInstrument
//---------------------------------------------------------
void Part::removeInstrument(const Fraction& tick)
{
auto i = _instruments.find(tick.ticks());
if (i == _instruments.end()) {
qDebug("Part::removeInstrument: not found at tick %d", tick.ticks());
return;
}
_instruments.erase(i);
}
void Part::removeInstrument(const QString& instrumentId)
{
for (auto it = _instruments.begin(); it != _instruments.end(); ++it) {
if (it->second->instrumentId() == instrumentId) {
_instruments.erase(it);
break;
}
}
}
//---------------------------------------------------------
// instrument
//---------------------------------------------------------
Instrument* Part::instrument(Fraction tick)
{
return _instruments.instrument(tick.ticks());
}
//---------------------------------------------------------
// instrument
//---------------------------------------------------------
const Instrument* Part::instrument(Fraction tick) const
{
return _instruments.instrument(tick.ticks());
}
//---------------------------------------------------------
// instruments
//---------------------------------------------------------
const InstrumentList* Part::instruments() const
{
return &_instruments;
}
bool Part::isDoublingInstrument(const QString& instrumentId) const
{
return instrument()->instrumentId() != instrumentId;
}
//---------------------------------------------------------
// instrumentId
//---------------------------------------------------------
QString Part::instrumentId(const Fraction& tick) const
{
return instrument(tick)->instrumentId();
}
//---------------------------------------------------------
// longName
//---------------------------------------------------------
QString Part::longName(const Fraction& tick) const
{
const QList<StaffName>& nl = longNames(tick);
return nl.empty() ? "" : nl[0].name();
}
//---------------------------------------------------------
// instrumentName
//---------------------------------------------------------
QString Part::instrumentName(const Fraction& tick) const
{
return instrument(tick)->trackName();
}
//---------------------------------------------------------
// shortName
//---------------------------------------------------------
QString Part::shortName(const Fraction& tick) const
{
const QList<StaffName>& nl = shortNames(tick);
return nl.empty() ? "" : nl[0].name();
}
//---------------------------------------------------------
// setLongName
//---------------------------------------------------------
void Part::setLongName(const QString& s)
{
instrument()->setLongName(s);
}
//---------------------------------------------------------
// setShortName
//---------------------------------------------------------
void Part::setShortName(const QString& s)
{
instrument()->setShortName(s);
}
//---------------------------------------------------------
// setPlainLongName
//---------------------------------------------------------
void Part::setPlainLongName(const QString& s)
{
setLongName(XmlWriter::xmlString(s));
}
//---------------------------------------------------------
// setPlainShortName
//---------------------------------------------------------
void Part::setPlainShortName(const QString& s)
{
setShortName(XmlWriter::xmlString(s));
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant Part::getProperty(Pid id) const
{
switch (id) {
case Pid::VISIBLE:
return QVariant(_show);
case Pid::USE_DRUMSET:
return instrument()->useDrumset();
case Pid::PREFER_SHARP_FLAT:
return int(preferSharpFlat());
default:
return QVariant();
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool Part::setProperty(Pid id, const QVariant& property)
{
switch (id) {
case Pid::VISIBLE:
setShow(property.toBool());
break;
case Pid::USE_DRUMSET:
instrument()->setUseDrumset(property.toBool());
break;
case Pid::PREFER_SHARP_FLAT:
setPreferSharpFlat(PreferSharpFlat(property.toInt()));
break;
default:
qDebug("Part::setProperty: unknown id %d", int(id));
break;
}
score()->setLayoutAll();
return true;
}
//---------------------------------------------------------
// startTrack
//---------------------------------------------------------
int Part::startTrack() const
{
return _staves.front()->idx() * VOICES;
}
//---------------------------------------------------------
// endTrack
//---------------------------------------------------------
int Part::endTrack() const
{
return _staves.back()->idx() * VOICES + VOICES;
}
//---------------------------------------------------------
// insertTime
//---------------------------------------------------------
void Part::insertTime(const Fraction& tick, const Fraction& len)
{
if (len.isZero()) {
return;
}
// move all instruments
if (len < Fraction(0, 1)) {
// remove instruments between tickpos >= tick and tickpos < (tick+len)
// ownership goes back to class InstrumentChange()
auto si = _instruments.lower_bound(tick.ticks());
auto ei = _instruments.lower_bound((tick - len).ticks());
_instruments.erase(si, ei);
}
InstrumentList il;
for (auto i = _instruments.lower_bound(tick.ticks()); i != _instruments.end();) {
Instrument* instrument = i->second;
int t = i->first;
_instruments.erase(i++);
_instruments[t + len.ticks()] = instrument;
}
_instruments.insert(il.begin(), il.end());
}
//---------------------------------------------------------
// lyricCount
//---------------------------------------------------------
int Part::lyricCount() const
{
if (!score()) {
return 0;
}
if (!score()->firstMeasure()) {
return 0;
}
size_t count = 0;
SegmentType st = SegmentType::ChordRest;
for (Segment* seg = score()->firstMeasure()->first(st); seg; seg = seg->next1(st)) {
for (int i = startTrack(); i < endTrack(); ++i) {
ChordRest* cr = toChordRest(seg->element(i));
if (cr) {
count += cr->lyrics().size();
}
}
}
return int(count);
}
//---------------------------------------------------------
// harmonyCount
//---------------------------------------------------------
int Part::harmonyCount() const
{
if (!score()) {
return 0;
}
Measure* firstM = score()->firstMeasure();
if (!firstM) {
return 0;
}
SegmentType st = SegmentType::ChordRest;
int count = 0;
for (const Segment* seg = firstM->first(st); seg; seg = seg->next1(st)) {
for (const Element* e : seg->annotations()) {
if ((e->isHarmony() || (e->isFretDiagram() && toFretDiagram(e)->harmony())) && e->track() >= startTrack()
&& e->track() < endTrack()) {
count++;
}
}
}
return count;
}
//---------------------------------------------------------
// updateHarmonyChannels
/// update the harmony channel by creating a new channel
/// when appropriate or using the existing one
///
/// checkRemoval can be set to true to check to see if we
/// can remove the harmony channel
//---------------------------------------------------------
void Part::updateHarmonyChannels(bool isDoOnInstrumentChanged, bool checkRemoval)
{
auto onInstrumentChanged = [this]() {
masterScore()->rebuildMidiMapping();
masterScore()->updateChannel();
score()->setInstrumentsChanged(true);
score()->setLayoutAll(); //do we need this?
};
// usage of harmony count is okay even if expensive since checking harmony channel will shortcircuit if existent
// harmonyCount will only be called on loading of a score (where it will need to be scanned for harmony anyway)
// or when the first harmony of a score is just added
if (checkRemoval) {
//may be a bit expensive since it gets called after every single delete or undo, but it should be okay for now
//~OPTIM~
if (harmonyCount() == 0) {
Instrument* instr = instrument();
int hChIdx = instr->channelIdx(Channel::HARMONY_NAME);
if (hChIdx != -1) {
Channel* hChan = instr->channel(hChIdx);
instr->removeChannel(hChan);
delete hChan;
if (isDoOnInstrumentChanged) {
onInstrumentChanged();
}
return;
}
}
}
if (!harmonyChannel() && harmonyCount() > 0) {
Instrument* instr = instrument();
Channel* c = new Channel(*instr->channel(0));
// default to program 0, which is piano in General MIDI
c->setProgram(0);
if (c->bank() == 128) { // drumset?
c->setBank(0);
}
c->setName(Channel::HARMONY_NAME);
instr->appendChannel(c);
onInstrumentChanged();
}
}
//---------------------------------------------------------
// harmonyChannel
//---------------------------------------------------------
const Channel* Part::harmonyChannel() const
{
const Instrument* instr = instrument();
if (!instr) {
return nullptr;
}
int chanIdx = instr->channelIdx(Channel::HARMONY_NAME);
if (chanIdx == -1) {
return nullptr;
}
const Channel* chan = instr->channel(chanIdx);
Q_ASSERT(chan);
return chan;
}
//---------------------------------------------------------
// hasPitchedStaff
//---------------------------------------------------------
bool Part::hasPitchedStaff() const
{
if (!staves()) {
return false;
}
for (Staff* s : *staves()) {
if (s && s->isPitchedStaff(Fraction(0, 1))) {
return true;
}
}
return false;
}
//---------------------------------------------------------
// hasTabStaff
//---------------------------------------------------------
bool Part::hasTabStaff() const
{
if (!staves()) {
return false;
}
for (Staff* s : *staves()) {
if (s && s->isTabStaff(Fraction(0, 1))) {
return true;
}
}
return false;
}
//---------------------------------------------------------
// hasDrumStaff
//---------------------------------------------------------
bool Part::hasDrumStaff() const
{
if (!staves()) {
return false;
}
for (Staff* s : *staves()) {
if (s && s->isDrumStaff(Fraction(0, 1))) {
return true;
}
}
return false;
}
}