1218 lines
58 KiB
C++
1218 lines
58 KiB
C++
//=============================================================================
|
||
// MuseScore
|
||
// Music Composition & Notation
|
||
//
|
||
// Copyright (C) 2009-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
|
||
//=============================================================================
|
||
|
||
#include "excerpt.h"
|
||
#include "score.h"
|
||
#include "part.h"
|
||
#include "xml.h"
|
||
#include "staff.h"
|
||
#include "box.h"
|
||
#include "textframe.h"
|
||
#include "style.h"
|
||
#include "page.h"
|
||
#include "text.h"
|
||
#include "slur.h"
|
||
#include "tie.h"
|
||
#include "sig.h"
|
||
#include "tempo.h"
|
||
#include "measure.h"
|
||
#include "rest.h"
|
||
#include "stafftype.h"
|
||
#include "tuplet.h"
|
||
#include "chord.h"
|
||
#include "note.h"
|
||
#include "lyrics.h"
|
||
#include "segment.h"
|
||
#include "tupletmap.h"
|
||
#include "tiemap.h"
|
||
#include "layoutbreak.h"
|
||
#include "harmony.h"
|
||
#include "beam.h"
|
||
#include "utils.h"
|
||
#include "tremolo.h"
|
||
#include "barline.h"
|
||
#include "undo.h"
|
||
#include "bracketItem.h"
|
||
|
||
namespace Ms {
|
||
|
||
//---------------------------------------------------------
|
||
// ~Excerpt
|
||
//---------------------------------------------------------
|
||
|
||
Excerpt::~Excerpt() {
|
||
delete _partScore;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// read
|
||
//---------------------------------------------------------
|
||
|
||
void Excerpt::read(XmlReader& e)
|
||
{
|
||
const QList<Part*>& pl = _oscore->parts();
|
||
QString name;
|
||
while (e.readNextStartElement()) {
|
||
const QStringRef& tag = e.name();
|
||
if (tag == "name")
|
||
name = e.readElementText();
|
||
else if (tag == "title")
|
||
_title = e.readElementText().trimmed();
|
||
else if (tag == "part") {
|
||
int partIdx = e.readInt();
|
||
if (partIdx < 0 || partIdx >= pl.size())
|
||
qDebug("Excerpt::read: bad part index");
|
||
else
|
||
_parts.append(pl.at(partIdx));
|
||
}
|
||
}
|
||
if (_title.isEmpty())
|
||
_title = name.trimmed();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// operator!=
|
||
//---------------------------------------------------------
|
||
|
||
bool Excerpt::operator!=(const Excerpt& e) const
|
||
{
|
||
if (e._oscore != _oscore)
|
||
return true;
|
||
if (e._title != _title)
|
||
return true;
|
||
if (e._parts != _parts)
|
||
return true;
|
||
if (e._tracks != _tracks)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// operator==
|
||
//---------------------------------------------------------
|
||
|
||
bool Excerpt::operator==(const Excerpt& e) const
|
||
{
|
||
if (e._oscore != _oscore)
|
||
return false;
|
||
if (e._title != _title)
|
||
return false;
|
||
if (e._parts != _parts)
|
||
return false;
|
||
if (e._tracks != _tracks)
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// localSetScore
|
||
//---------------------------------------------------------
|
||
|
||
static void localSetScore(void* score, Element* element)
|
||
{
|
||
element->setScore((Score*)score);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// createExcerpt
|
||
//---------------------------------------------------------
|
||
|
||
void Excerpt::createExcerpt(Excerpt* excerpt)
|
||
{
|
||
MasterScore* oscore = excerpt->oscore();
|
||
Score* score = excerpt->partScore();
|
||
|
||
QList<Part*>& parts = excerpt->parts();
|
||
QList<int> srcStaves;
|
||
|
||
// clone layer:
|
||
for (int i = 0; i < 32; ++i) {
|
||
score->layerTags()[i] = oscore->layerTags()[i];
|
||
score->layerTagComments()[i] = oscore->layerTagComments()[i];
|
||
}
|
||
score->setCurrentLayer(oscore->currentLayer());
|
||
score->layer().clear();
|
||
foreach (const Layer& l, oscore->layer())
|
||
score->layer().append(l);
|
||
|
||
score->setPageNumberOffset(oscore->pageNumberOffset());
|
||
|
||
// Set instruments and create linked staffs
|
||
for (const Part* part : parts) {
|
||
Part* p = new Part(score);
|
||
p->setInstrument(*part->instrument());
|
||
p->setPartName(part->partName());
|
||
|
||
for (Staff* staff : *part->staves()) {
|
||
Staff* s = new Staff(score);
|
||
s->setPart(p);
|
||
// s->setStaffType(0, *staff->staffType(0)); // TODO
|
||
s->init(staff);
|
||
s->setDefaultClefType(staff->defaultClefType());
|
||
// the order of staff - s matters as staff should be the first entry in the
|
||
// created link list to make primaryStaff() work
|
||
// TODO: change implementation, maybe create an explicit "primary" flag
|
||
score->undo(new Link(s, staff));
|
||
p->staves()->append(s);
|
||
score->staves().append(s);
|
||
srcStaves.append(staff->idx());
|
||
}
|
||
score->appendPart(p);
|
||
}
|
||
|
||
// Fill tracklist (map all tracks of a stave)
|
||
if (excerpt->tracks().isEmpty()) {
|
||
QMultiMap<int, int> tracks;
|
||
for (Staff* s : score->staves()) {
|
||
const LinkedElements* ls = s->links();
|
||
if (ls == 0)
|
||
continue;
|
||
for (auto le : *ls) {
|
||
Staff* ps = toStaff(le);
|
||
if (ps->primaryStaff()) {
|
||
for (int i = 0; i < VOICES; i++) {
|
||
tracks.insert(ps->idx() * VOICES + i % VOICES, s->idx() * VOICES + i % VOICES);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
excerpt->setTracks(tracks);
|
||
}
|
||
|
||
cloneStaves(oscore, score, srcStaves, excerpt->tracks());
|
||
|
||
// create excerpt title and title frame for all scores if not already there
|
||
MeasureBase* measure = oscore->first();
|
||
|
||
if (!measure || !measure->isVBox()) {
|
||
qDebug("original score has no header frame");
|
||
oscore->insertMeasure(ElementType::VBOX, measure);
|
||
measure = oscore->first();
|
||
}
|
||
VBox* titleFrameScore = toVBox(measure);
|
||
|
||
measure = score->first();
|
||
Q_ASSERT(measure->isVBox());
|
||
|
||
VBox* titleFramePart = toVBox(measure);
|
||
titleFramePart->copyValues(titleFrameScore);
|
||
QString partLabel = excerpt->title(); // parts.front()->longName();
|
||
if (!partLabel.isEmpty()) {
|
||
Text* txt = new Text(score, Tid::INSTRUMENT_EXCERPT);
|
||
txt->setPlainText(partLabel);
|
||
txt->setTrack(0);
|
||
measure->add(txt);
|
||
score->setMetaTag("partName", partLabel);
|
||
}
|
||
|
||
// layout score
|
||
score->addLayoutFlags(LayoutFlag::FIX_PITCH_VELO);
|
||
score->doLayout();
|
||
|
||
// handle transposing instruments
|
||
if (oscore->styleB(Sid::concertPitch) != score->styleB(Sid::concertPitch)) {
|
||
for (const Staff* staff : score->staves()) {
|
||
if (staff->staffType(0)->group() == StaffGroup::PERCUSSION)
|
||
continue;
|
||
|
||
// if this staff has no transposition, and no instrument changes, we can skip it
|
||
Interval interval = staff->part()->instrument()->transpose();
|
||
if (interval.isZero() && staff->part()->instruments()->size() == 1)
|
||
continue;
|
||
bool flip = false;
|
||
if (oscore->styleB(Sid::concertPitch)) {
|
||
interval.flip(); // flip the transposition for the original instrument
|
||
flip = true; // transposeKeys() will flip transposition for each instrument change
|
||
}
|
||
|
||
int staffIdx = staff->idx();
|
||
int startTrack = staffIdx * VOICES;
|
||
int endTrack = startTrack + VOICES;
|
||
|
||
int endTick = 0;
|
||
if (score->lastSegment())
|
||
endTick = score->lastSegment()->tick();
|
||
score->transposeKeys(staffIdx, staffIdx+1, 0, endTick, interval, true, flip);
|
||
|
||
for (auto segment = score->firstSegmentMM(SegmentType::ChordRest); segment; segment = segment->next1MM(SegmentType::ChordRest)) {
|
||
interval = staff->part()->instrument(segment->tick())->transpose();
|
||
if (interval.isZero())
|
||
continue;
|
||
if (oscore->styleB(Sid::concertPitch))
|
||
interval.flip();
|
||
|
||
for (auto e : segment->annotations()) {
|
||
if (!e->isHarmony() || (e->track() < startTrack) || (e->track() >= endTrack))
|
||
continue;
|
||
Harmony* h = toHarmony(e);
|
||
int rootTpc = Ms::transposeTpc(h->rootTpc(), interval, true);
|
||
int baseTpc = Ms::transposeTpc(h->baseTpc(), interval, true);
|
||
// mmrests are on by default in part
|
||
// if this harmony is attached to an mmrest,
|
||
// be sure to transpose harmony in underlying measure as well
|
||
for (ScoreElement* se : h->linkList()) {
|
||
Harmony* hh = static_cast<Harmony*>(se);
|
||
// skip links to other staves (including in other scores)
|
||
if (hh->staff() != h->staff())
|
||
continue;
|
||
score->undoTransposeHarmony(hh, rootTpc, baseTpc);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// layout score
|
||
score->setPlaylistDirty();
|
||
oscore->rebuildMidiMapping();
|
||
oscore->updateChannel();
|
||
|
||
score->setLayoutAll();
|
||
score->doLayout();
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// deleteExcerpt
|
||
//---------------------------------------------------------
|
||
|
||
void MasterScore::deleteExcerpt(Excerpt* excerpt)
|
||
{
|
||
Q_ASSERT(excerpt->oscore() == this);
|
||
Score* partScore = excerpt->partScore();
|
||
|
||
if (!partScore) {
|
||
qDebug("deleteExcerpt: no partScore");
|
||
return;
|
||
}
|
||
|
||
// unlink the staves in the excerpt
|
||
for (Staff* st : partScore->staves()) {
|
||
bool hasLinksInMaster = false;
|
||
if (st->links()) {
|
||
for (auto le : *st->links()) {
|
||
if (le->score() == this) {
|
||
hasLinksInMaster = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (hasLinksInMaster) {
|
||
int staffIdx = st->idx();
|
||
// unlink the spanners
|
||
for (auto i = partScore->spanner().begin(); i != partScore->spanner().cend(); ++i) {
|
||
Spanner* sp = i->second;
|
||
if (sp->staffIdx() == staffIdx)
|
||
sp->undoUnlink();
|
||
}
|
||
int sTrack = staffIdx * VOICES;
|
||
int eTrack = sTrack + VOICES;
|
||
// unlink elements and annotation
|
||
for (Segment* s = partScore->firstSegmentMM(SegmentType::All); s; s = s->next1MM()) {
|
||
for (int track = eTrack - 1; track >= sTrack; --track) {
|
||
Element* el = s->element(track);
|
||
if (el)
|
||
el->undoUnlink();
|
||
}
|
||
for (Element* e : s->annotations()) {
|
||
if (e->staffIdx() == staffIdx)
|
||
e->undoUnlink();
|
||
}
|
||
}
|
||
// unlink the staff
|
||
undo(new Unlink(st));
|
||
}
|
||
}
|
||
undo(new RemoveExcerpt(excerpt));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// cloneSpanner
|
||
//---------------------------------------------------------
|
||
|
||
static void cloneSpanner(Spanner* s, Score* score, int dstTrack, int dstTrack2)
|
||
{
|
||
// don’t clone voltas for track != 0
|
||
if (s->type() == ElementType::VOLTA && s->track() != 0)
|
||
return;
|
||
Spanner* ns = toSpanner(s->linkedClone());
|
||
ns->setScore(score);
|
||
ns->setParent(0);
|
||
ns->setTrack(dstTrack);
|
||
ns->setTrack2(dstTrack2);
|
||
|
||
if (ns->type() == ElementType::SLUR) {
|
||
|
||
// set start/end element for slur
|
||
ChordRest* cr1 = s->startCR();
|
||
ChordRest* cr2 = s->endCR();
|
||
|
||
ns->setStartElement(0);
|
||
ns->setEndElement(0);
|
||
if (cr1 && cr1->links()) {
|
||
for (ScoreElement* e : *cr1->links()) {
|
||
ChordRest* cr = toChordRest(e);
|
||
if (cr == cr1)
|
||
continue;
|
||
if ((cr->score() == score) && (cr->tick() == ns->tick()) && cr->track() == dstTrack) {
|
||
ns->setStartElement(cr);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (cr2 && cr2->links()) {
|
||
for (ScoreElement* e : *cr2->links()) {
|
||
ChordRest* cr = toChordRest(e);
|
||
if (cr == cr2)
|
||
continue;
|
||
if ((cr->score() == score) && (cr->tick() == ns->tick2()) && cr->track() == dstTrack2) {
|
||
ns->setEndElement(cr);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (!ns->startElement())
|
||
qDebug("clone Slur: no start element");
|
||
if (!ns->endElement())
|
||
qDebug("clone Slur: no end element");
|
||
}
|
||
score->undo(new AddElement(ns));
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// cloneTuplets
|
||
//---------------------------------------------------------
|
||
|
||
static void cloneTuplets(ChordRest* ocr, ChordRest* ncr, Tuplet* ot, TupletMap& tupletMap, Measure* m, int track)
|
||
{
|
||
ot->setTrack(ocr->track());
|
||
Tuplet* nt = tupletMap.findNew(ot);
|
||
if (nt == 0) {
|
||
nt = toTuplet(ot->linkedClone());
|
||
nt->setTrack(track);
|
||
nt->setParent(m);
|
||
nt->setScore(ncr->score());
|
||
tupletMap.add(ot, nt);
|
||
|
||
Tuplet* nt1 = nt;
|
||
while (ot->tuplet()) {
|
||
Tuplet* nt2 = tupletMap.findNew(ot->tuplet());
|
||
if (nt2 == 0) {
|
||
nt2 = toTuplet(ot->tuplet()->linkedClone());
|
||
nt2->setTrack(track);
|
||
nt2->setParent(m);
|
||
nt2->setScore(ncr->score());
|
||
tupletMap.add(ot->tuplet(), nt2);
|
||
}
|
||
nt2->add(nt1);
|
||
nt1->setTuplet(nt2);
|
||
ot = ot->tuplet();
|
||
nt1 = nt2;
|
||
}
|
||
}
|
||
nt->add(ncr);
|
||
ncr->setTuplet(nt);
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// cloneStaves
|
||
//---------------------------------------------------------
|
||
|
||
void Excerpt::cloneStaves(Score* oscore, Score* score, const QList<int>& map, QMultiMap<int, int>& trackList)
|
||
{
|
||
TieMap tieMap;
|
||
|
||
MeasureBaseList* nmbl = score->measures();
|
||
for (MeasureBase* mb = oscore->measures()->first(); mb; mb = mb->next()) {
|
||
MeasureBase* nmb = 0;
|
||
if (mb->isHBox())
|
||
nmb = new HBox(score);
|
||
else if (mb->isVBox())
|
||
nmb = new VBox(score);
|
||
else if (mb->isTBox()) {
|
||
nmb = new TBox(score);
|
||
Text* text = toTBox(mb)->text();
|
||
Element* ne = text->linkedClone();
|
||
ne->setScore(score);
|
||
nmb->add(ne);
|
||
}
|
||
else if (mb->isMeasure()) {
|
||
Measure* m = toMeasure(mb);
|
||
Measure* nm = new Measure(score);
|
||
nmb = nm;
|
||
nm->setTick(m->tick());
|
||
nm->setLen(m->len());
|
||
nm->setTimesig(m->timesig());
|
||
|
||
nm->setRepeatCount(m->repeatCount());
|
||
nm->setRepeatStart(m->repeatStart());
|
||
nm->setRepeatEnd(m->repeatEnd());
|
||
nm->setRepeatJump(m->repeatJump());
|
||
|
||
nm->setIrregular(m->irregular());
|
||
nm->setNo(m->no());
|
||
nm->setNoOffset(m->noOffset());
|
||
nm->setBreakMultiMeasureRest(m->breakMultiMeasureRest());
|
||
//TODO nm->setEndBarLineType(
|
||
// m->endBarLineType(),
|
||
// m->endBarLineGenerated(),
|
||
// m->endBarLineVisible(),
|
||
// m->endBarLineColor());
|
||
|
||
// Fraction ts = nm->len();
|
||
int tracks = oscore->nstaves() * VOICES;
|
||
for (int srcTrack = 0; srcTrack < tracks; ++srcTrack) {
|
||
TupletMap tupletMap; // tuplets cannot cross measure boundaries
|
||
|
||
int strack = trackList.value(srcTrack, -1);
|
||
|
||
Tremolo* tremolo = 0;
|
||
for (Segment* oseg = m->first(); oseg; oseg = oseg->next()) {
|
||
Segment* ns = nullptr; //create segment later, on demand
|
||
for (Element* e : oseg->annotations()) {
|
||
if (e->generated())
|
||
continue;
|
||
if ((e->track() == srcTrack && strack != -1) || (e->systemFlag() && srcTrack == 0)) {
|
||
Element* ne = e->linkedClone();
|
||
// reset offset as most likely it will not fit
|
||
PropertyFlags f = ne->propertyFlags(Pid::OFFSET);
|
||
if (f == PropertyFlags::UNSTYLED)
|
||
ne->setPropertyFlags(Pid::OFFSET, PropertyFlags::STYLED);
|
||
ne->setOffset(QPointF());
|
||
ne->setTrack(strack == -1 ? 0 : strack);
|
||
ne->setScore(score);
|
||
if (!ns)
|
||
ns = nm->getSegment(oseg->segmentType(), oseg->tick());
|
||
ns->add(ne);
|
||
// for chord symbols,
|
||
// re-render with new style settings
|
||
if (ne->isHarmony()) {
|
||
Harmony* h = toHarmony(ne);
|
||
h->render();
|
||
}
|
||
}
|
||
}
|
||
|
||
//If track is not mapped skip the following
|
||
if (trackList.value(srcTrack, -1) == -1)
|
||
continue;
|
||
|
||
//There are probably more destination tracks for the same source
|
||
QList<int> t = trackList.values(srcTrack);
|
||
|
||
for (int track : t) {
|
||
//Clone KeySig TimeSig and Clefs if voice 1 of source staff is not mapped to a track
|
||
Element* oef = oseg->element(srcTrack & ~3);
|
||
if (oef && (oef->isTimeSig() || oef->isKeySig()) && oef->tick() == 0
|
||
&& !(trackList.size() == (score->excerpt()->parts().size() * VOICES))) {
|
||
Element* ne = oef->linkedClone();
|
||
ne->setTrack(track & ~3);
|
||
ne->setScore(score);
|
||
ns = nm->getSegment(oseg->segmentType(), oseg->tick());
|
||
ns->add(ne);
|
||
}
|
||
|
||
Element* oe = oseg->element(srcTrack);
|
||
int adjustedBarlineSpan = 0;
|
||
if (srcTrack % VOICES == 0 && oseg->segmentType() == SegmentType::BarLine) {
|
||
// mid-measure barline segment
|
||
// may need to clone barline from a previous staff and/or adjust span
|
||
int oIdx = srcTrack / VOICES;
|
||
if (!oe) {
|
||
// no barline on this staff in original score,
|
||
// but check previous staves
|
||
for (int i = oIdx - 1; i >= 0; --i) {
|
||
oe = oseg->element(i * VOICES);
|
||
if (oe)
|
||
break;
|
||
}
|
||
}
|
||
if (oe) {
|
||
// barline found, now check span
|
||
BarLine* bl = toBarLine(oe);
|
||
int oSpan1 = bl->staff()->idx();
|
||
int oSpan2 = oSpan1 + bl->spanStaff();
|
||
if (oSpan1 <= oIdx && oIdx < oSpan2) {
|
||
// this staff is within span
|
||
// calculate adjusted span for excerpt
|
||
int oSpan = oSpan2 - oIdx;
|
||
adjustedBarlineSpan = qMin(oSpan, score->nstaves());
|
||
}
|
||
else {
|
||
// this staff is not within span
|
||
oe = nullptr;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (oe && !oe->generated()) {
|
||
Element* ne;
|
||
ne = oe->linkedClone();
|
||
ne->setTrack(track);
|
||
|
||
if (!(ne->track() % VOICES) && ne->isRest())
|
||
toRest(ne)->setGap(false);
|
||
|
||
ne->setScore(score);
|
||
if (oe->type() == ElementType::BAR_LINE && adjustedBarlineSpan) {
|
||
BarLine* nbl = toBarLine(ne);
|
||
nbl->setSpanStaff(adjustedBarlineSpan);
|
||
}
|
||
else if (oe->isChordRest()) {
|
||
ChordRest* ocr = toChordRest(oe);
|
||
ChordRest* ncr = toChordRest(ne);
|
||
|
||
if (ocr->beam() && !ocr->beam()->empty() && ocr->beam()->elements().front() == ocr) {
|
||
Beam* nb = ocr->beam()->clone();
|
||
nb->clear();
|
||
nb->setTrack(track);
|
||
nb->setScore(score);
|
||
nb->add(ncr);
|
||
ncr->setBeam(nb);
|
||
}
|
||
|
||
Tuplet* ot = ocr->tuplet();
|
||
|
||
if (ot)
|
||
cloneTuplets(ocr, ncr, ot, tupletMap, nm, track);
|
||
|
||
if (oe->isChord()) {
|
||
Chord* och = toChord(ocr);
|
||
Chord* nch = toChord(ncr);
|
||
|
||
size_t n = och->notes().size();
|
||
for (size_t i = 0; i < n; ++i) {
|
||
Note* on = och->notes().at(i);
|
||
Note* nn = nch->notes().at(i);
|
||
if (on->tieFor()) {
|
||
Tie* tie = toTie(on->tieFor()->linkedClone());
|
||
tie->setScore(score);
|
||
nn->setTieFor(tie);
|
||
tie->setStartNote(nn);
|
||
tie->setTrack(nn->track());
|
||
tieMap.add(on->tieFor(), tie);
|
||
}
|
||
if (on->tieBack()) {
|
||
Tie* tie = tieMap.findNew(on->tieBack());
|
||
if (tie) {
|
||
nn->setTieBack(tie);
|
||
tie->setEndNote(nn);
|
||
}
|
||
else {
|
||
qDebug("cloneStaves: cannot find tie");
|
||
}
|
||
}
|
||
// add back spanners (going back from end to start spanner element
|
||
// makes sure the 'other' spanner anchor element is already set up)
|
||
// 'on' is the old spanner end note and 'nn' is the new spanner end note
|
||
for (Spanner* oldSp : on->spannerBack()) {
|
||
if (oldSp->startElement() && oldSp->endElement() && oldSp->startElement()->track() > oldSp->endElement()->track())
|
||
continue;
|
||
Note* newStart = Spanner::startElementFromSpanner(oldSp, nn);
|
||
if (newStart != nullptr) {
|
||
Spanner* newSp = toSpanner(oldSp->linkedClone());
|
||
newSp->setNoteSpan(newStart, nn);
|
||
score->addElement(newSp);
|
||
}
|
||
else {
|
||
qDebug("cloneStaves: cannot find spanner start note");
|
||
}
|
||
}
|
||
for (Spanner* oldSp : on->spannerFor()) {
|
||
if (oldSp->startElement() && oldSp->endElement() && oldSp->startElement()->track() <= oldSp->endElement()->track())
|
||
continue;
|
||
Note* newEnd = Spanner::endElementFromSpanner(oldSp, nn);
|
||
if (newEnd != nullptr) {
|
||
Spanner* newSp = toSpanner(oldSp->linkedClone());
|
||
newSp->setNoteSpan(nn, newEnd);
|
||
score->addElement(newSp);
|
||
}
|
||
else {
|
||
qDebug("cloneStaves: cannot find spanner end note");
|
||
}
|
||
}
|
||
}
|
||
// two note tremolo
|
||
if (och->tremolo() && och->tremolo()->twoNotes()) {
|
||
if (och == och->tremolo()->chord1()) {
|
||
if (tremolo)
|
||
qDebug("unconnected two note tremolo");
|
||
tremolo = toTremolo(och->tremolo()->linkedClone());
|
||
tremolo->setScore(nch->score());
|
||
tremolo->setParent(nch);
|
||
tremolo->setTrack(nch->track());
|
||
tremolo->setChords(nch, 0);
|
||
nch->setTremolo(tremolo);
|
||
}
|
||
else if (och == och->tremolo()->chord2()) {
|
||
if (!tremolo)
|
||
qDebug("first note for two note tremolo missing");
|
||
else {
|
||
tremolo->setChords(tremolo->chord1(), nch);
|
||
nch->setTremolo(tremolo);
|
||
}
|
||
}
|
||
else
|
||
qDebug("inconsistent two note tremolo");
|
||
}
|
||
}
|
||
}
|
||
if (!ns)
|
||
ns = nm->getSegment(oseg->segmentType(), oseg->tick());
|
||
ns->add(ne);
|
||
}
|
||
|
||
Segment* tst = nm->segments().firstCRSegment();
|
||
if (srcTrack % VOICES && !(track % VOICES) && (!tst || (!tst->element(track)))) {
|
||
Rest* rest = new Rest(score);
|
||
rest->setDuration(nm->len());
|
||
rest->setDurationType(nm->len().ticks());
|
||
rest->setTrack(track);
|
||
Segment* segment = nm->getSegment(SegmentType::ChordRest, nm->tick());
|
||
segment->add(rest);
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
nmb->linkTo(mb);
|
||
for (Element* e : mb->el()) {
|
||
if (e->isLayoutBreak()) {
|
||
LayoutBreak::Type st = toLayoutBreak(e)->layoutBreakType();
|
||
if (st == LayoutBreak::Type::PAGE || st == LayoutBreak::Type::LINE)
|
||
continue;
|
||
}
|
||
int track = -1;
|
||
if (e->track() != -1) {
|
||
// try to map track
|
||
track = trackList.value(e->track(), -1);
|
||
if (track == -1) {
|
||
// even if track not in excerpt, we need to clone system elements
|
||
if (e->systemFlag())
|
||
track = 0;
|
||
else
|
||
continue;
|
||
}
|
||
}
|
||
|
||
Element* ne;
|
||
// link text - title, subtitle, also repeats (eg, coda/segno)
|
||
// measure numbers are not stored in this list, but they should not be cloned anyhow
|
||
// layout breaks other than section were skipped above,
|
||
// but section breaks do need to be cloned & linked
|
||
// other measure-attached elements (?) are cloned but not linked
|
||
if (e->isTextBase() || e->isLayoutBreak()) {
|
||
ne = e->clone();
|
||
ne->setAutoplace(true);
|
||
ne->linkTo(e);
|
||
}
|
||
else
|
||
ne = e->clone();
|
||
ne->setScore(score);
|
||
ne->setTrack(track);
|
||
nmb->add(ne);
|
||
}
|
||
nmbl->add(nmb);
|
||
}
|
||
|
||
int n = map.size();
|
||
for (int dstStaffIdx = 0; dstStaffIdx < n; ++dstStaffIdx) {
|
||
Staff* srcStaff = oscore->staff(map[dstStaffIdx]);
|
||
Staff* dstStaff = score->staff(dstStaffIdx);
|
||
|
||
if (srcStaff->primaryStaff()) {
|
||
int span = srcStaff->barLineSpan();
|
||
int sIdx = srcStaff->idx();
|
||
if (dstStaffIdx == 0 && span == 0) {
|
||
// this is first staff of new score,
|
||
// but it was somewhere within a barline span in the old score
|
||
// so, find beginning of span
|
||
for (int i = 0; i <= sIdx; ++i) {
|
||
span = oscore->staff(i)->barLineSpan();
|
||
if (i + span > sIdx) {
|
||
sIdx = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
int eIdx = sIdx + span;
|
||
for (int staffIdx = sIdx; staffIdx < eIdx; ++staffIdx) {
|
||
if (!map.contains(staffIdx))
|
||
--span;
|
||
}
|
||
if (dstStaffIdx + span > n)
|
||
span = n - dstStaffIdx - 1;
|
||
dstStaff->setBarLineSpan(span);
|
||
int idx = 0;
|
||
for (BracketItem* bi : srcStaff->brackets()) {
|
||
dstStaff->setBracketType(idx, bi->bracketType());
|
||
dstStaff->setBracketSpan(idx, bi->bracketSpan());
|
||
}
|
||
}
|
||
}
|
||
|
||
for (auto i : oscore->spanner()) {
|
||
Spanner* s = i.second;
|
||
int dstTrack = -1;
|
||
int dstTrack2 = -1;
|
||
|
||
if (s->type() == ElementType::VOLTA) {
|
||
//always export voltas to first staff in part
|
||
dstTrack = 0;
|
||
dstTrack2 = 0;
|
||
cloneSpanner(s, score, dstTrack, dstTrack2);
|
||
}
|
||
else if (s->isHairpin()) {
|
||
//always export these spanners to first voice of the destination staff
|
||
|
||
QList<int> track1;
|
||
for (int ii = s->track(); ii < s->track() + VOICES; ii++) {
|
||
track1 += trackList.values(ii);
|
||
}
|
||
|
||
for (int track : track1) {
|
||
if (!(track % VOICES))
|
||
cloneSpanner(s, score, track, track);
|
||
}
|
||
|
||
}
|
||
else {
|
||
if (trackList.value(s->track(), -1) == -1 || trackList.value(s->track2(), -1) == -1)
|
||
continue;
|
||
QList<int> track1 = trackList.values(s->track());
|
||
QList<int> track2 = trackList.values(s->track2());
|
||
|
||
if (track1.length() != track2.length())
|
||
continue;
|
||
|
||
//export other spanner if staffidx matches
|
||
for (int ii = 0; ii < track1.length(); ii++) {
|
||
dstTrack = track1.at(ii);
|
||
dstTrack2 = track2.at(ii);
|
||
cloneSpanner(s, score, dstTrack, dstTrack2);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// cloneStaff
|
||
// staves are in same score
|
||
//---------------------------------------------------------
|
||
|
||
void Excerpt::cloneStaff(Staff* srcStaff, Staff* dstStaff)
|
||
{
|
||
Score* score = srcStaff->score();
|
||
TieMap tieMap;
|
||
|
||
score->undo(new Link(dstStaff, srcStaff));
|
||
|
||
int srcStaffIdx = srcStaff->idx();
|
||
int dstStaffIdx = dstStaff->idx();
|
||
|
||
for (Measure* m = score->firstMeasure(); m; m = m->nextMeasure()) {
|
||
int sTrack = srcStaffIdx * VOICES;
|
||
int eTrack = sTrack + VOICES;
|
||
for (int srcTrack = sTrack; srcTrack < eTrack; ++srcTrack) {
|
||
TupletMap tupletMap; // tuplets cannot cross measure boundaries
|
||
int dstTrack = dstStaffIdx * VOICES + (srcTrack - sTrack);
|
||
Tremolo* tremolo = 0;
|
||
for (Segment* seg = m->first(); seg; seg = seg->next()) {
|
||
Element* oe = seg->element(srcTrack);
|
||
if (oe == 0 || oe->generated())
|
||
continue;
|
||
if (oe->isTimeSig())
|
||
continue;
|
||
Element* ne = 0;
|
||
if (oe->isClef()) {
|
||
// only clone clef if it matches staff group and does not exists yet
|
||
Clef* clef = toClef(oe);
|
||
int tick = seg->tick();
|
||
if (ClefInfo::staffGroup(clef->concertClef()) == dstStaff->constStaffType(0)->group()
|
||
&& dstStaff->clefType(tick) != clef->clefTypeList()) {
|
||
ne = oe->clone();
|
||
}
|
||
}
|
||
else
|
||
ne = oe->linkedClone();
|
||
if (ne) {
|
||
ne->setTrack(dstTrack);
|
||
ne->setParent(seg);
|
||
ne->setScore(score);
|
||
if (ne->isChordRest()) {
|
||
ChordRest* ncr = toChordRest(ne);
|
||
if (ncr->tuplet()) {
|
||
ncr->setTuplet(0); //TODO nested tuplets
|
||
}
|
||
}
|
||
score->undoAddElement(ne);
|
||
}
|
||
if (oe->isChordRest()) {
|
||
ChordRest* ocr = toChordRest(oe);
|
||
ChordRest* ncr = toChordRest(ne);
|
||
Tuplet* ot = ocr->tuplet();
|
||
if (ot)
|
||
cloneTuplets(ocr, ncr, ot, tupletMap, m, dstTrack);
|
||
|
||
// remove lyrics from chord
|
||
// since only one set of lyrics is used with linked staves
|
||
foreach (Lyrics* l, ncr->lyrics()) {
|
||
if (l)
|
||
l->unlink();
|
||
}
|
||
qDeleteAll(ncr->lyrics());
|
||
ncr->lyrics().clear();
|
||
|
||
for (Element* e : seg->annotations()) {
|
||
if (e->generated() || e->systemFlag())
|
||
continue;
|
||
if (e->track() != srcTrack)
|
||
continue;
|
||
switch (e->type()) {
|
||
// exclude certain element types
|
||
// this should be same list excluded in Score::undoAddElement()
|
||
case ElementType::STAFF_TEXT:
|
||
case ElementType::SYSTEM_TEXT:
|
||
case ElementType::FRET_DIAGRAM:
|
||
case ElementType::HARMONY:
|
||
case ElementType::FIGURED_BASS:
|
||
case ElementType::DYNAMIC:
|
||
case ElementType::LYRICS: // not normally segment-attached
|
||
continue;
|
||
default:
|
||
Element* ne1 = e->clone();
|
||
ne1->setTrack(dstTrack);
|
||
ne1->setParent(seg);
|
||
ne1->setScore(score);
|
||
score->undoAddElement(ne1);
|
||
}
|
||
}
|
||
if (oe->isChord()) {
|
||
Chord* och = toChord(ocr);
|
||
Chord* nch = toChord(ncr);
|
||
size_t n = och->notes().size();
|
||
for (size_t i = 0; i < n; ++i) {
|
||
Note* on = och->notes().at(i);
|
||
Note* nn = nch->notes().at(i);
|
||
if (on->tieFor()) {
|
||
Tie* tie = toTie(on->tieFor()->linkedClone());
|
||
tie->setScore(score);
|
||
nn->setTieFor(tie);
|
||
tie->setStartNote(nn);
|
||
tie->setTrack(nn->track());
|
||
tieMap.add(on->tieFor(), tie);
|
||
}
|
||
if (on->tieBack()) {
|
||
Tie* tie = tieMap.findNew(on->tieBack());
|
||
if (tie) {
|
||
nn->setTieBack(tie);
|
||
tie->setEndNote(nn);
|
||
}
|
||
else {
|
||
qDebug("cloneStave: cannot find tie");
|
||
}
|
||
}
|
||
// add back spanners (going back from end to start spanner element
|
||
// makes sure the 'other' spanner anchor element is already set up)
|
||
// 'on' is the old spanner end note and 'nn' is the new spanner end note
|
||
for (Spanner* oldSp : on->spannerBack()) {
|
||
Note* newStart = Spanner::startElementFromSpanner(oldSp, nn);
|
||
if (newStart != nullptr) {
|
||
Spanner* newSp = toSpanner(oldSp->linkedClone());
|
||
newSp->setNoteSpan(newStart, nn);
|
||
score->addElement(newSp);
|
||
}
|
||
else {
|
||
qDebug("cloneStave: cannot find spanner start note");
|
||
}
|
||
}
|
||
}
|
||
// two note tremolo
|
||
if (och->tremolo() && och->tremolo()->twoNotes()) {
|
||
if (och == och->tremolo()->chord1()) {
|
||
if (tremolo)
|
||
qDebug("unconnected two note tremolo");
|
||
tremolo = toTremolo(och->tremolo()->linkedClone());
|
||
tremolo->setScore(nch->score());
|
||
tremolo->setParent(nch);
|
||
tremolo->setTrack(nch->track());
|
||
tremolo->setChords(nch, 0);
|
||
nch->setTremolo(tremolo);
|
||
}
|
||
else if (och == och->tremolo()->chord2()) {
|
||
if (!tremolo)
|
||
qDebug("first note for two note tremolo missing");
|
||
else {
|
||
tremolo->setChords(tremolo->chord1(), nch);
|
||
nch->setTremolo(tremolo);
|
||
}
|
||
}
|
||
else
|
||
qDebug("inconsistent two note tremolo");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (auto i : score->spanner()) {
|
||
Spanner* s = i.second;
|
||
int staffIdx = s->staffIdx();
|
||
int dstTrack = -1;
|
||
int dstTrack2 = -1;
|
||
if (s->type() != ElementType::VOLTA) {
|
||
//export other spanner if staffidx matches
|
||
if (srcStaffIdx == staffIdx) {
|
||
dstTrack = dstStaffIdx * VOICES + s->voice();
|
||
dstTrack2 = dstStaffIdx * VOICES + (s->track2() % VOICES);
|
||
}
|
||
}
|
||
if (dstTrack == -1)
|
||
continue;
|
||
cloneSpanner(s, score, dstTrack, dstTrack2);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// cloneStaff2
|
||
// staves are potentially in different scores
|
||
//---------------------------------------------------------
|
||
|
||
void Excerpt::cloneStaff2(Staff* srcStaff, Staff* dstStaff, int stick, int etick)
|
||
{
|
||
Score* oscore = srcStaff->score();
|
||
Score* score = dstStaff->score();
|
||
|
||
Excerpt* oex = oscore->excerpt();
|
||
Excerpt* ex = score->excerpt();
|
||
QMultiMap<int, int> otracks, tracks;
|
||
if (oex)
|
||
otracks = oex->tracks();
|
||
if (ex)
|
||
tracks = ex->tracks();
|
||
|
||
Measure* m1 = oscore->tick2measure(stick);
|
||
Measure* m2 = oscore->tick2measure(etick);
|
||
|
||
if (m2->tick() < etick) // end of score
|
||
m2 = 0;
|
||
|
||
TieMap tieMap;
|
||
|
||
int srcStaffIdx = srcStaff->idx();
|
||
int dstStaffIdx = dstStaff->idx();
|
||
|
||
int sTrack = srcStaffIdx * VOICES;
|
||
int eTrack = sTrack + VOICES;
|
||
|
||
QMap<int, int> map;
|
||
for (int i = sTrack; i < eTrack; i++) {
|
||
if (!oex && !ex)
|
||
map.insert(i, dstStaffIdx * VOICES + i % VOICES);
|
||
else if (oex && !ex) {
|
||
if (otracks.key(i, -1) != -1)
|
||
map.insert(i, otracks.key(i));
|
||
}
|
||
else if (!oex && ex) {
|
||
for (int j : tracks.values(i)) {
|
||
if (dstStaffIdx * VOICES <= j && j < (dstStaffIdx + 1) * VOICES) {
|
||
map.insert(i, j);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else if (oex && ex) {
|
||
if (otracks.key(i, -1) != -1) {
|
||
for (int j : tracks.values(otracks.key(i))) {
|
||
if (dstStaffIdx * VOICES <= j && j < (dstStaffIdx + 1) * VOICES) {
|
||
map.insert(i, j);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
for (Measure* m = m1; m && (m != m2); m = m->nextMeasure()) {
|
||
Measure* nm = score->tick2measure(m->tick());
|
||
for (int srcTrack : map.keys()) {
|
||
TupletMap tupletMap; // tuplets cannot cross measure boundaries
|
||
int dstTrack = map.value(srcTrack);
|
||
for (Segment* oseg = m->first(); oseg; oseg = oseg->next()) {
|
||
Element* oe = oseg->element(srcTrack);
|
||
if (oe == 0 || oe->generated())
|
||
continue;
|
||
if (oe->type() == ElementType::TIMESIG)
|
||
continue;
|
||
Segment* ns = nm->getSegment(oseg->segmentType(), oseg->tick());
|
||
Element* ne = oe->linkedClone();
|
||
ne->setTrack(dstTrack);
|
||
ne->setParent(ns);
|
||
ne->setScore(score);
|
||
score->undoAddElement(ne);
|
||
if (oe->isChordRest()) {
|
||
ChordRest* ocr = toChordRest(oe);
|
||
ChordRest* ncr = toChordRest(ne);
|
||
Tuplet* ot = ocr->tuplet();
|
||
if (ot) {
|
||
Tuplet* nt = tupletMap.findNew(ot);
|
||
if (nt == 0) {
|
||
// nt = new Tuplet(*ot);
|
||
nt = toTuplet(ot->linkedClone());
|
||
nt->clear();
|
||
nt->setTrack(dstTrack);
|
||
nt->setParent(m);
|
||
tupletMap.add(ot, nt);
|
||
}
|
||
ncr->setTuplet(nt);
|
||
nt->add(ncr);
|
||
}
|
||
|
||
for (Element* e : oseg->annotations()) {
|
||
if (e->generated() || e->systemFlag())
|
||
continue;
|
||
if (e->track() != srcTrack)
|
||
continue;
|
||
switch (e->type()) {
|
||
// exclude certain element types
|
||
// this should be same list excluded in Score::undoAddElement()
|
||
case ElementType::STAFF_TEXT:
|
||
case ElementType::SYSTEM_TEXT:
|
||
case ElementType::FRET_DIAGRAM:
|
||
case ElementType::HARMONY:
|
||
case ElementType::FIGURED_BASS:
|
||
case ElementType::DYNAMIC:
|
||
case ElementType::LYRICS: // not normally segment-attached
|
||
continue;
|
||
default:
|
||
Element* ne1 = e->clone();
|
||
ne1->setTrack(dstTrack);
|
||
ne1->setParent(ns);
|
||
ne1->setScore(score);
|
||
score->undoAddElement(ne1);
|
||
}
|
||
}
|
||
if (oe->isChord()) {
|
||
Chord* och = toChord(ocr);
|
||
Chord* nch = toChord(ncr);
|
||
size_t n = och->notes().size();
|
||
for (size_t i = 0; i < n; ++i) {
|
||
Note* on = och->notes().at(i);
|
||
Note* nn = nch->notes().at(i);
|
||
if (on->tieFor()) {
|
||
Tie* tie = toTie(on->tieFor()->linkedClone());
|
||
tie->setScore(score);
|
||
nn->setTieFor(tie);
|
||
tie->setStartNote(nn);
|
||
tie->setTrack(nn->track());
|
||
tieMap.add(on->tieFor(), tie);
|
||
}
|
||
if (on->tieBack()) {
|
||
Tie* tie = tieMap.findNew(on->tieBack());
|
||
if (tie) {
|
||
nn->setTieBack(tie);
|
||
tie->setEndNote(nn);
|
||
}
|
||
else {
|
||
qDebug("cloneStave: cannot find tie");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (auto i : oscore->spanner()) {
|
||
Spanner* s = i.second;
|
||
if (!(s->tick() >= stick && s->tick2() < etick))
|
||
continue;
|
||
|
||
int staffIdx = s->staffIdx();
|
||
int dstTrack = -1;
|
||
int dstTrack2 = -1;
|
||
if (s->type() != ElementType::VOLTA) {
|
||
//export other spanner if staffidx matches
|
||
if (srcStaffIdx == staffIdx) {
|
||
dstTrack = dstStaffIdx * VOICES + s->voice();
|
||
dstTrack2 = dstStaffIdx * VOICES + (s->track2() % VOICES);
|
||
}
|
||
}
|
||
if (dstTrack == -1)
|
||
continue;
|
||
cloneSpanner(s, score, dstTrack, dstTrack2);
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// createAllExcerpt
|
||
//---------------------------------------------------------
|
||
|
||
QList<Excerpt*> Excerpt::createAllExcerpt(MasterScore *score)
|
||
{
|
||
QList<Excerpt*> all;
|
||
for (Part* part : score->parts()) {
|
||
if (part->show()) {
|
||
Excerpt* e = new Excerpt(score);
|
||
e->parts().append(part);
|
||
for (int i = part->startTrack(), j = 0; i < part->endTrack(); i++, j++) {
|
||
e->tracks().insert(i, j);
|
||
}
|
||
QString name = createName(part->partName(), all);
|
||
e->setTitle(name);
|
||
all.append(e);
|
||
}
|
||
}
|
||
return all;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// createName
|
||
//---------------------------------------------------------
|
||
|
||
QString Excerpt::createName(const QString& partName, QList<Excerpt*>& excerptList)
|
||
{
|
||
QString name = partName.simplified();
|
||
int count = 0; // no of occurrences of partName
|
||
|
||
for (Excerpt* e : excerptList) {
|
||
// if <partName> already exists, change <partName> to <partName 1>
|
||
if (e->title().compare(name) == 0)
|
||
e->setTitle(e->title() + " 1");
|
||
|
||
QRegExp rx("^(.+)\\s\\d+$");
|
||
if (rx.indexIn(e->title()) > -1 && rx.cap(1) == name)
|
||
count++;
|
||
}
|
||
|
||
if (count > 0)
|
||
name += QString(" %1").arg(count + 1);
|
||
|
||
return name;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// setPartScore
|
||
//---------------------------------------------------------
|
||
|
||
void Excerpt::setPartScore(Score* s)
|
||
{
|
||
_partScore = s;
|
||
s->setExcerpt(this);
|
||
}
|
||
|
||
}
|