[engraving] moved layout Beam
This commit is contained in:
parent
bfbee737f6
commit
d58dfe5a81
18 changed files with 1549 additions and 1511 deletions
|
@ -156,8 +156,10 @@ set(MODULE_SRC
|
|||
${CMAKE_CURRENT_LIST_DIR}/layout/layoutlyrics.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/layoutmeasure.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/layoutmeasure.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/layoutbeams.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/layoutbeams.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/beamlayout.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/beamlayout.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/beamtremololayout.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/beamtremololayout.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/layouttuplets.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/layouttuplets.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/layout/layoutchords.cpp
|
||||
|
|
1470
src/engraving/layout/beamlayout.cpp
Normal file
1470
src/engraving/layout/beamlayout.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -19,11 +19,13 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef MU_ENGRAVING_LAYOUTBEAMS_H
|
||||
#define MU_ENGRAVING_LAYOUTBEAMS_H
|
||||
#ifndef MU_ENGRAVING_BEAMLAYOUT_H
|
||||
#define MU_ENGRAVING_BEAMLAYOUT_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "types/types.h"
|
||||
|
||||
namespace mu::engraving {
|
||||
class Beam;
|
||||
class Chord;
|
||||
|
@ -34,21 +36,33 @@ class Score;
|
|||
class Segment;
|
||||
|
||||
class LayoutContext;
|
||||
class LayoutBeams
|
||||
class BeamLayout
|
||||
{
|
||||
public:
|
||||
|
||||
static void layout(Beam* item, LayoutContext& ctx);
|
||||
static void layout1(Beam* item, LayoutContext& ctx);
|
||||
|
||||
static bool isTopBeam(ChordRest* cr);
|
||||
static bool notTopBeam(ChordRest* cr);
|
||||
static void createBeams(Score* score, LayoutContext& lc, Measure* measure);
|
||||
static void restoreBeams(Measure* m);
|
||||
static void breakCrossMeasureBeams(const LayoutContext& ctx, Measure* measure);
|
||||
static void breakCrossMeasureBeams(LayoutContext& ctx, Measure* measure);
|
||||
static void layoutNonCrossBeams(Segment* s);
|
||||
static void verticalAdjustBeamedRests(Rest* rest, Beam* beam);
|
||||
|
||||
private:
|
||||
static void beamGraceNotes(Score* score, Chord* mainNote, bool after);
|
||||
|
||||
static void layout2(Beam* item, const std::vector<ChordRest*>& chordRests, SpannerSegmentType, int frag);
|
||||
|
||||
static void createBeamSegments(Beam* item, const std::vector<ChordRest*>& chordRests);
|
||||
static bool calcIsBeamletBefore(const Beam* item, Chord* chord, int i, int level, bool isAfter32Break, bool isAfter64Break);
|
||||
static void createBeamSegment(Beam* item, ChordRest* startChord, ChordRest* endChord, int level);
|
||||
static void createBeamletSegment(Beam* item, ChordRest* chord, bool isBefore, int level);
|
||||
|
||||
static bool layout2Cross(Beam *item, const std::vector<ChordRest*>& chordRests, int frag);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MU_ENGRAVING_LAYOUTBEAMS_H
|
||||
#endif // MU_ENGRAVING_BEAMLAYOUT_H
|
|
@ -20,14 +20,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "beam.h"
|
||||
#include "chordrest.h"
|
||||
#include "note.h"
|
||||
#include "score.h"
|
||||
#include "staff.h"
|
||||
#include "stem.h"
|
||||
#include "stemslash.h"
|
||||
#include "tremolo.h"
|
||||
#include "../libmscore/beam.h"
|
||||
#include "../libmscore/chordrest.h"
|
||||
#include "../libmscore/note.h"
|
||||
#include "../libmscore/score.h"
|
||||
#include "../libmscore/staff.h"
|
||||
#include "../libmscore/stem.h"
|
||||
#include "../libmscore/stemslash.h"
|
||||
#include "../libmscore/tremolo.h"
|
||||
|
||||
#include "log.h"
|
||||
|
|
@ -23,10 +23,10 @@
|
|||
#ifndef __BEAMTREMOLOLAYOUT_H__
|
||||
#define __BEAMTREMOLOLAYOUT_H__
|
||||
|
||||
#include "beam.h"
|
||||
#include "engravingitem.h"
|
||||
#include "durationtype.h"
|
||||
#include "property.h"
|
||||
#include "../libmscore/beam.h"
|
||||
#include "../libmscore/engravingitem.h"
|
||||
#include "../libmscore/durationtype.h"
|
||||
#include "../libmscore/property.h"
|
||||
|
||||
namespace mu::engraving {
|
||||
class Chord;
|
|
@ -46,7 +46,7 @@
|
|||
#include "layoutpage.h"
|
||||
#include "layoutmeasure.h"
|
||||
#include "layoutsystem.h"
|
||||
#include "layoutbeams.h"
|
||||
#include "beamlayout.h"
|
||||
#include "layouttuplets.h"
|
||||
|
||||
#include "log.h"
|
||||
|
@ -472,7 +472,7 @@ void Layout::layoutLinear(const LayoutOptions& options, LayoutContext& ctx)
|
|||
continue;
|
||||
}
|
||||
ChordRest* cr = toChordRest(e);
|
||||
if (LayoutBeams::notTopBeam(cr)) { // layout cross staff beams
|
||||
if (BeamLayout::notTopBeam(cr)) { // layout cross staff beams
|
||||
cr->beam()->layout();
|
||||
}
|
||||
if (LayoutTuplets::notTopTuplet(cr)) {
|
||||
|
|
|
@ -1,584 +0,0 @@
|
|||
/*
|
||||
* 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 "layoutbeams.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "containers.h"
|
||||
|
||||
#include "libmscore/beam.h"
|
||||
#include "libmscore/tremolo.h"
|
||||
#include "libmscore/chord.h"
|
||||
#include "libmscore/factory.h"
|
||||
#include "libmscore/measure.h"
|
||||
#include "libmscore/rest.h"
|
||||
#include "libmscore/score.h"
|
||||
#include "libmscore/segment.h"
|
||||
#include "libmscore/staff.h"
|
||||
#include "libmscore/system.h"
|
||||
#include "libmscore/timesig.h"
|
||||
#include "libmscore/utils.h"
|
||||
|
||||
#include "layoutcontext.h"
|
||||
#include "layoutchords.h"
|
||||
|
||||
using namespace mu::engraving;
|
||||
|
||||
//---------------------------------------------------------
|
||||
// isTopBeam
|
||||
// returns true for the first CR of a beam that is not cross-staff
|
||||
//---------------------------------------------------------
|
||||
|
||||
bool LayoutBeams::isTopBeam(ChordRest* cr)
|
||||
{
|
||||
Beam* b = cr->beam();
|
||||
if (b && b->elements().front() == cr) {
|
||||
// beam already considered cross?
|
||||
if (b->cross()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// for beams not already considered cross,
|
||||
// consider them so here if any elements were moved up
|
||||
for (ChordRest* cr1 : b->elements()) {
|
||||
// some element moved up?
|
||||
if (cr1->staffMove() < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// not cross
|
||||
return true;
|
||||
}
|
||||
|
||||
// no beam or not first element
|
||||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// notTopBeam
|
||||
// returns true for the first CR of a beam that is cross-staff
|
||||
//---------------------------------------------------------
|
||||
|
||||
bool LayoutBeams::notTopBeam(ChordRest* cr)
|
||||
{
|
||||
Beam* b = cr->beam();
|
||||
if (b && b->elements().front() == cr) {
|
||||
// beam already considered cross?
|
||||
if (b->cross()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// for beams not already considered cross,
|
||||
// consider them so here if any elements were moved up
|
||||
for (ChordRest* cr1 : b->elements()) {
|
||||
// some element moved up?
|
||||
if (cr1->staffMove() < 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// not cross
|
||||
return false;
|
||||
}
|
||||
|
||||
// no beam or not first element
|
||||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// restoreBeams
|
||||
//---------------------------------------------------------
|
||||
|
||||
void LayoutBeams::restoreBeams(Measure* m)
|
||||
{
|
||||
for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
|
||||
for (EngravingItem* e : s->elist()) {
|
||||
if (e && e->isChordRest()) {
|
||||
ChordRest* cr = toChordRest(e);
|
||||
Beam* b = cr->beam();
|
||||
if (b && !b->elements().empty() && b->elements().front() == cr) {
|
||||
b->layout();
|
||||
b->addSkyline(m->system()->staff(b->staffIdx())->skyline());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// breakCrossMeasureBeams
|
||||
//---------------------------------------------------------
|
||||
|
||||
void LayoutBeams::breakCrossMeasureBeams(const LayoutContext& ctx, Measure* measure)
|
||||
{
|
||||
MeasureBase* mbNext = measure->next();
|
||||
if (!mbNext || !mbNext->isMeasure()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Measure* next = toMeasure(mbNext);
|
||||
const size_t ntracks = ctx.score()->ntracks();
|
||||
Segment* fstSeg = next->first(SegmentType::ChordRest);
|
||||
if (!fstSeg) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t track = 0; track < ntracks; ++track) {
|
||||
Staff* stf = ctx.score()->staff(track2staff(track));
|
||||
|
||||
// don’t compute beams for invisible staves and tablature without stems
|
||||
if (!stf->show() || (stf->isTabStaff(measure->tick()) && stf->staffType(measure->tick())->stemless())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EngravingItem* e = fstSeg->element(static_cast<int>(track));
|
||||
if (!e || !e->isChordRest()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ChordRest* cr = toChordRest(e);
|
||||
Beam* beam = cr->beam();
|
||||
if (!beam || beam->elements().front()->measure() == next) { // no beam or not cross-measure beam
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<ChordRest*> mElements;
|
||||
std::vector<ChordRest*> nextElements;
|
||||
|
||||
for (ChordRest* beamCR : beam->elements()) {
|
||||
if (beamCR->measure() == measure) {
|
||||
mElements.push_back(beamCR);
|
||||
} else {
|
||||
nextElements.push_back(beamCR);
|
||||
}
|
||||
}
|
||||
|
||||
if (mElements.size() == 1) {
|
||||
mElements[0]->removeDeleteBeam(false);
|
||||
}
|
||||
|
||||
Beam* newBeam = nullptr;
|
||||
if (nextElements.size() > 1) {
|
||||
newBeam = Factory::createBeam(ctx.score()->dummy()->system());
|
||||
newBeam->setGenerated(true);
|
||||
newBeam->setTrack(track);
|
||||
}
|
||||
|
||||
const bool nextBeamed = bool(newBeam);
|
||||
for (ChordRest* nextCR : nextElements) {
|
||||
nextCR->removeDeleteBeam(nextBeamed);
|
||||
if (newBeam) {
|
||||
newBeam->add(nextCR);
|
||||
}
|
||||
}
|
||||
|
||||
if (newBeam) {
|
||||
newBeam->layout1();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool beamNoContinue(BeamMode mode)
|
||||
{
|
||||
return mode == BeamMode::END || mode == BeamMode::NONE || mode == BeamMode::INVALID;
|
||||
}
|
||||
|
||||
#define beamModeMid(a) (a == BeamMode::MID || a == BeamMode::BEGIN32 || a == BeamMode::BEGIN64)
|
||||
|
||||
//---------------------------------------------------------
|
||||
// beamGraceNotes
|
||||
//---------------------------------------------------------
|
||||
|
||||
void LayoutBeams::beamGraceNotes(Score* score, Chord* mainNote, bool after)
|
||||
{
|
||||
ChordRest* a1 = 0; // start of (potential) beam
|
||||
Beam* beam = 0; // current beam
|
||||
BeamMode bm = BeamMode::AUTO;
|
||||
std::vector<Chord*> graceNotes = after ? mainNote->graceNotesAfter() : mainNote->graceNotesBefore();
|
||||
|
||||
if (beam) {
|
||||
beam->setIsGrace(true);
|
||||
}
|
||||
|
||||
for (ChordRest* cr : graceNotes) {
|
||||
bm = Groups::endBeam(cr);
|
||||
if ((cr->durationType().type() <= DurationType::V_QUARTER) || (bm == BeamMode::NONE)) {
|
||||
if (beam) {
|
||||
beam->setIsGrace(true);
|
||||
beam->layout1();
|
||||
beam = 0;
|
||||
}
|
||||
if (a1) {
|
||||
a1->removeDeleteBeam(false);
|
||||
a1 = 0;
|
||||
}
|
||||
cr->removeDeleteBeam(false);
|
||||
continue;
|
||||
}
|
||||
if (beam) {
|
||||
bool beamEnd = bm == BeamMode::BEGIN;
|
||||
if (!beamEnd) {
|
||||
cr->replaceBeam(beam);
|
||||
cr = 0;
|
||||
beamEnd = (bm == BeamMode::END);
|
||||
}
|
||||
if (beamEnd) {
|
||||
beam->setIsGrace(true);
|
||||
beam->layout1();
|
||||
beam = 0;
|
||||
}
|
||||
}
|
||||
if (!cr) {
|
||||
continue;
|
||||
}
|
||||
if (a1 == 0) {
|
||||
a1 = cr;
|
||||
} else {
|
||||
if (!beamModeMid(bm) && (bm == BeamMode::BEGIN)) {
|
||||
a1->removeDeleteBeam(false);
|
||||
a1 = cr;
|
||||
} else {
|
||||
beam = a1->beam();
|
||||
if (beam == 0 || beam->elements().front() != a1) {
|
||||
beam = Factory::createBeam(score->dummy()->system());
|
||||
beam->setGenerated(true);
|
||||
beam->setTrack(mainNote->track());
|
||||
a1->replaceBeam(beam);
|
||||
}
|
||||
cr->replaceBeam(beam);
|
||||
a1 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (beam) {
|
||||
beam->setIsGrace(true);
|
||||
beam->layout1();
|
||||
} else if (a1) {
|
||||
a1->removeDeleteBeam(false);
|
||||
}
|
||||
}
|
||||
|
||||
void LayoutBeams::createBeams(Score* score, LayoutContext& lc, Measure* measure)
|
||||
{
|
||||
bool crossMeasure = score->styleB(Sid::crossMeasureValues);
|
||||
|
||||
for (track_idx_t track = 0; track < score->ntracks(); ++track) {
|
||||
Staff* stf = score->staff(track2staff(track));
|
||||
|
||||
// don’t compute beams for invisible staves and tablature without stems
|
||||
if (!stf->show() || (stf->isTabStaff(measure->tick()) && stf->staffType(measure->tick())->stemless())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ChordRest* a1 = nullptr; // start of (potential) beam
|
||||
bool firstCR = true;
|
||||
Beam* beam = nullptr; // current beam
|
||||
BeamMode bm = BeamMode::AUTO;
|
||||
ChordRest* prev = nullptr;
|
||||
bool checkBeats = false;
|
||||
Fraction stretch = Fraction(1, 1);
|
||||
std::unordered_map<int, TDuration> beatSubdivision;
|
||||
|
||||
// if this measure is simple meter (actually X/4),
|
||||
// then perform a prepass to determine the subdivision of each beat
|
||||
|
||||
beatSubdivision.clear();
|
||||
TimeSig* ts = stf->timeSig(measure->tick());
|
||||
checkBeats = false;
|
||||
stretch = ts ? ts->stretch() : Fraction(1, 1);
|
||||
|
||||
const SegmentType st = SegmentType::ChordRest;
|
||||
if (ts && ts->denominator() == 4) {
|
||||
checkBeats = true;
|
||||
for (Segment* s = measure->first(st); s; s = s->next(st)) {
|
||||
ChordRest* mcr = toChordRest(s->element(track));
|
||||
if (mcr == 0) {
|
||||
continue;
|
||||
}
|
||||
int beat = (mcr->rtick() * stretch).ticks() / Constants::division;
|
||||
if (mu::contains(beatSubdivision, beat)) {
|
||||
beatSubdivision[beat] = std::min(beatSubdivision[beat], mcr->durationType());
|
||||
} else {
|
||||
beatSubdivision[beat] = mcr->durationType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Segment* segment = measure->first(st); segment; segment = segment->next(st)) {
|
||||
ChordRest* cr = segment->cr(track);
|
||||
if (!cr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstCR) {
|
||||
firstCR = false;
|
||||
// Handle cross-measure beams
|
||||
BeamMode mode = cr->beamMode();
|
||||
if (mode == BeamMode::MID || mode == BeamMode::END || mode == BeamMode::BEGIN32 || mode == BeamMode::BEGIN64) {
|
||||
ChordRest* prevCR = score->findCR(measure->tick() - Fraction::fromTicks(1), track);
|
||||
if (prevCR) {
|
||||
Beam* prevBeam = prevCR->beam();
|
||||
const Measure* pm = prevCR->measure();
|
||||
if (!beamNoContinue(prevCR->beamMode())
|
||||
&& !pm->lineBreak() && !pm->pageBreak() && !pm->sectionBreak()
|
||||
&& lc.prevMeasure
|
||||
&& !(prevCR->isChord() && prevCR->durationType().type() <= DurationType::V_QUARTER)) {
|
||||
beam = prevBeam;
|
||||
//a1 = beam ? beam->elements().front() : prevCR;
|
||||
a1 = beam ? nullptr : prevCR; // when beam is found, a1 is no longer required.
|
||||
} else if (prevBeam && prevBeam == cr->beam() && prevBeam->elements().front() == prevCR) {
|
||||
// remove the beam from the previous chordrest because we do not currently
|
||||
// support cross-system beams
|
||||
prevCR->removeDeleteBeam(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hasAllRest = cr->beam() && cr->beam()->hasAllRests();
|
||||
bool isARestBeamStart = cr->isRest() && cr->beamMode() == BeamMode::BEGIN;
|
||||
if (hasAllRest && !isARestBeamStart) {
|
||||
cr->removeDeleteBeam(false);
|
||||
}
|
||||
|
||||
// handle grace notes and cross-measure beaming
|
||||
// (tied chords?)
|
||||
if (cr->isChord()) {
|
||||
Chord* chord = toChord(cr);
|
||||
beamGraceNotes(score, chord, false); // grace before
|
||||
beamGraceNotes(score, chord, true); // grace after
|
||||
// set up for cross-measure values as soon as possible
|
||||
// to have all computations (stems, hooks, ...) consistent with it
|
||||
if (!chord->isGrace()) {
|
||||
chord->crossMeasureSetup(crossMeasure);
|
||||
}
|
||||
}
|
||||
|
||||
if (cr->isRest() && cr->beamMode() == BeamMode::AUTO) {
|
||||
bm = BeamMode::NONE; // do not beam rests set to BeamMode::AUTO or with only other rests
|
||||
} else {
|
||||
bm = Groups::endBeam(cr, prev); // get defaults from time signature properties
|
||||
}
|
||||
// perform additional context-dependent checks
|
||||
if (bm == BeamMode::AUTO) {
|
||||
// check if we need to break beams according to minimum duration in current / previous beat
|
||||
if (checkBeats && cr->rtick().isNotZero()) {
|
||||
Fraction tick = cr->rtick() * stretch;
|
||||
// check if on the beat
|
||||
if ((tick.ticks() % Constants::division) == 0) {
|
||||
int beat = tick.ticks() / Constants::division;
|
||||
// get minimum duration for this & previous beat
|
||||
TDuration minDuration = std::min(beatSubdivision[beat], beatSubdivision[beat - 1]);
|
||||
// re-calculate beam as if this were the duration of current chordrest
|
||||
TDuration saveDuration = cr->actualDurationType();
|
||||
TDuration saveCMDuration = cr->crossMeasureDurationType();
|
||||
CrossMeasure saveCrossMeasVal = cr->crossMeasure();
|
||||
cr->setDurationType(minDuration);
|
||||
bm = Groups::endBeam(cr, prev);
|
||||
cr->setDurationType(saveDuration);
|
||||
cr->setCrossMeasure(saveCrossMeasVal);
|
||||
cr->setCrossMeasureDurationType(saveCMDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prev = cr;
|
||||
|
||||
// if chord has hooks and is 2nd element of a cross-measure value
|
||||
// set beam mode to NONE (do not combine with following chord beam/hook, if any)
|
||||
|
||||
if (cr->durationType().hooks() > 0 && cr->crossMeasure() == CrossMeasure::SECOND) {
|
||||
bm = BeamMode::NONE;
|
||||
}
|
||||
|
||||
if ((cr->durationType().type() <= DurationType::V_QUARTER) || (bm == BeamMode::NONE)) {
|
||||
bool removeBeam = true;
|
||||
if (beam) {
|
||||
beam->layout1();
|
||||
removeBeam = (beam->elements().size() <= 1 || beam->hasAllRests());
|
||||
beam = 0;
|
||||
}
|
||||
if (a1) {
|
||||
if (removeBeam) {
|
||||
a1->removeDeleteBeam(false);
|
||||
}
|
||||
a1 = 0;
|
||||
}
|
||||
cr->removeDeleteBeam(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (beam) {
|
||||
bool beamEnd = (bm == BeamMode::BEGIN);
|
||||
if (!beamEnd) {
|
||||
cr->replaceBeam(beam);
|
||||
cr = 0;
|
||||
beamEnd = (bm == BeamMode::END);
|
||||
}
|
||||
if (beamEnd) {
|
||||
beam->layout1();
|
||||
beam = 0;
|
||||
}
|
||||
}
|
||||
if (!cr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (a1 == 0) {
|
||||
a1 = cr;
|
||||
} else {
|
||||
if (!beamModeMid(bm)
|
||||
&&
|
||||
(bm == BeamMode::BEGIN
|
||||
|| (a1->segment()->segmentType() != cr->segment()->segmentType())
|
||||
|| (a1->tick() + a1->actualTicks() < cr->tick())
|
||||
)
|
||||
) {
|
||||
a1->removeDeleteBeam(false);
|
||||
a1 = cr;
|
||||
} else {
|
||||
beam = a1->beam();
|
||||
if (beam == 0 || beam->elements().front() != a1) {
|
||||
beam = Factory::createBeam(score->dummy()->system());
|
||||
beam->setGenerated(true);
|
||||
beam->setTrack(track);
|
||||
a1->replaceBeam(beam);
|
||||
}
|
||||
cr->replaceBeam(beam);
|
||||
a1 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (beam) {
|
||||
beam->layout1();
|
||||
} else if (a1) {
|
||||
Fraction nextTick = a1->tick() + a1->actualTicks();
|
||||
Measure* m = (nextTick >= measure->endTick() ? measure->nextMeasure() : measure);
|
||||
ChordRest* nextCR = (m ? m->findChordRest(nextTick, track) : nullptr);
|
||||
Beam* b = a1->beam();
|
||||
if (!(b && !b->elements().empty() && !b->hasAllRests() && b->elements().front() == a1 && nextCR
|
||||
&& beamModeMid(nextCR->beamMode()))) {
|
||||
a1->removeDeleteBeam(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************
|
||||
* layoutNonCrossBeams()
|
||||
* layout all non-cross-staff beams starting on this segment
|
||||
* **********************************************************/
|
||||
|
||||
void LayoutBeams::layoutNonCrossBeams(Segment* s)
|
||||
{
|
||||
for (EngravingItem* e : s->elist()) {
|
||||
if (!e || !e->isChordRest() || !e->score()->staff(e->staffIdx())->show()) {
|
||||
// the beam and its system may still be referenced when selecting all,
|
||||
// even if the staff is invisible. The old system is invalid and does cause problems in #284012
|
||||
if (e && e->isChordRest() && !e->score()->staff(e->staffIdx())->show() && toChordRest(e)->beam()) {
|
||||
toChordRest(e)->beam()->resetExplicitParent();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ChordRest* cr = toChordRest(e);
|
||||
// layout beam
|
||||
if (LayoutBeams::isTopBeam(cr)) {
|
||||
cr->beam()->layout();
|
||||
if (!cr->beam()->tremAnchors().empty()) {
|
||||
// there are inset tremolos in here
|
||||
for (ChordRest* beamCr : cr->beam()->elements()) {
|
||||
if (!beamCr->isChord()) {
|
||||
continue;
|
||||
}
|
||||
Chord* c = toChord(beamCr);
|
||||
if (c->tremolo() && c->tremolo()->twoNotes()) {
|
||||
c->tremolo()->layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cr->isChord()) {
|
||||
continue;
|
||||
}
|
||||
for (Chord* grace : toChord(cr)->graceNotes()) {
|
||||
if (LayoutBeams::isTopBeam(grace)) {
|
||||
grace->beam()->layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LayoutBeams::verticalAdjustBeamedRests(Rest* rest, Beam* beam)
|
||||
{
|
||||
const double spatium = rest->spatium();
|
||||
static constexpr Fraction rest32nd(1, 32);
|
||||
const bool up = beam->up();
|
||||
|
||||
double restToBeamPadding;
|
||||
if (rest->ticks() <= rest32nd) {
|
||||
restToBeamPadding = 0.2 * spatium;
|
||||
} else {
|
||||
restToBeamPadding = 0.35 * spatium;
|
||||
}
|
||||
|
||||
Shape beamShape = beam->shape().translated(beam->pagePos());
|
||||
mu::remove_if(beamShape, [&](ShapeElement& el) {
|
||||
return el.toItem && el.toItem->isBeamSegment() && toBeamSegment(el.toItem)->isBeamlet;
|
||||
});
|
||||
|
||||
Shape restShape = rest->shape().translated(rest->pagePos() - rest->offset());
|
||||
|
||||
double restToBeamClearance = up ? beamShape.verticalClearance(restShape) : restShape.verticalClearance(beamShape);
|
||||
if (restToBeamClearance > restToBeamPadding) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (up) {
|
||||
rest->verticalClearance().setAbove(restToBeamClearance);
|
||||
} else {
|
||||
rest->verticalClearance().setBelow(restToBeamClearance);
|
||||
}
|
||||
|
||||
bool restIsLocked = rest->verticalClearance().locked();
|
||||
if (!restIsLocked) {
|
||||
double overlap = (restToBeamPadding - restToBeamClearance);
|
||||
double lineDistance = rest->staff()->lineDistance(rest->tick()) * spatium;
|
||||
int lineMoves = ceil(overlap / lineDistance);
|
||||
lineMoves *= up ? 1 : -1;
|
||||
double yMove = lineMoves * lineDistance;
|
||||
rest->movePosY(yMove);
|
||||
for (Rest* mergedRest : rest->mergedRests()) {
|
||||
mergedRest->movePosY(yMove);
|
||||
}
|
||||
|
||||
Segment* segment = rest->segment();
|
||||
staff_idx_t staffIdx = rest->vStaffIdx();
|
||||
Score* score = rest->score();
|
||||
std::vector<Chord*> chords;
|
||||
std::vector<Rest*> rests;
|
||||
collectChordsAndRest(segment, staffIdx, chords, rests);
|
||||
LayoutChords::resolveRestVSChord(rests, chords, score, segment, staffIdx);
|
||||
LayoutChords::resolveRestVSRest(rests, score, segment, staffIdx, /*considerBeams*/ true);
|
||||
}
|
||||
beam->layout();
|
||||
}
|
|
@ -38,7 +38,7 @@
|
|||
#include "libmscore/undo.h"
|
||||
|
||||
#include "layoutcontext.h"
|
||||
#include "layoutbeams.h"
|
||||
#include "beamlayout.h"
|
||||
#include "layoutchords.h"
|
||||
#include "layouttremolo.h"
|
||||
|
||||
|
@ -824,7 +824,7 @@ void LayoutMeasure::getNextMeasure(const LayoutOptions& options, LayoutContext&
|
|||
}
|
||||
}
|
||||
|
||||
LayoutBeams::createBeams(score, ctx, measure);
|
||||
BeamLayout::createBeams(score, ctx, measure);
|
||||
/* HACK: The real beam layout is computed at much later stage (you can't do the beams until you know
|
||||
* horizontal spacing). However, horizontal spacing needs to know stems extensions to avoid collision
|
||||
* with stems, and stems extensions depend on beams. Solution: we compute dummy beams here, *before*
|
||||
|
@ -834,7 +834,7 @@ void LayoutMeasure::getNextMeasure(const LayoutOptions& options, LayoutContext&
|
|||
if (!s.isChordRestType()) {
|
||||
continue;
|
||||
}
|
||||
LayoutBeams::layoutNonCrossBeams(&s);
|
||||
BeamLayout::layoutNonCrossBeams(&s);
|
||||
}
|
||||
|
||||
for (staff_idx_t staffIdx = 0; staffIdx < score->nstaves(); ++staffIdx) {
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
#include "libmscore/tuplet.h"
|
||||
|
||||
#include "layoutsystem.h"
|
||||
#include "layoutbeams.h"
|
||||
#include "beamlayout.h"
|
||||
#include "layouttuplets.h"
|
||||
#include "verticalgapdata.h"
|
||||
|
||||
|
@ -273,7 +273,7 @@ void LayoutPage::collectPage(const LayoutOptions& options, LayoutContext& ctx)
|
|||
continue;
|
||||
}
|
||||
ChordRest* cr = toChordRest(e);
|
||||
if (LayoutBeams::notTopBeam(cr)) { // layout cross staff beams
|
||||
if (BeamLayout::notTopBeam(cr)) { // layout cross staff beams
|
||||
cr->beam()->layout();
|
||||
}
|
||||
if (LayoutTuplets::notTopTuplet(cr)) {
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
#include "libmscore/tuplet.h"
|
||||
#include "libmscore/volta.h"
|
||||
|
||||
#include "layoutbeams.h"
|
||||
#include "beamlayout.h"
|
||||
#include "layoutchords.h"
|
||||
#include "layoutharmonies.h"
|
||||
#include "layoutlyrics.h"
|
||||
|
@ -376,7 +376,7 @@ System* LayoutSystem::collectSystem(const LayoutOptions& options, LayoutContext&
|
|||
m->computeWidth(m->system()->minSysTicks(), m->system()->maxSysTicks(), oldStretch);
|
||||
m->stretchToTargetWidth(oldWidth);
|
||||
m->layoutMeasureElements();
|
||||
LayoutBeams::restoreBeams(m);
|
||||
BeamLayout::restoreBeams(m);
|
||||
if (m == nm || !m->noBreak()) {
|
||||
break;
|
||||
}
|
||||
|
@ -396,7 +396,7 @@ System* LayoutSystem::collectSystem(const LayoutOptions& options, LayoutContext&
|
|||
// Create end barlines
|
||||
if (ctx.prevMeasure && ctx.prevMeasure->isMeasure()) {
|
||||
Measure* pm = toMeasure(ctx.prevMeasure);
|
||||
LayoutBeams::breakCrossMeasureBeams(ctx, pm);
|
||||
BeamLayout::breakCrossMeasureBeams(ctx, pm);
|
||||
pm->createEndBarLines(true);
|
||||
}
|
||||
|
||||
|
@ -719,7 +719,7 @@ void LayoutSystem::layoutSystemElements(const LayoutOptions& options, LayoutCont
|
|||
if (!s->isChordRestType()) {
|
||||
continue;
|
||||
}
|
||||
LayoutBeams::layoutNonCrossBeams(s);
|
||||
BeamLayout::layoutNonCrossBeams(s);
|
||||
// Must recreate the shapes because stem lengths may have been changed!
|
||||
s->createShapes();
|
||||
}
|
||||
|
@ -732,7 +732,7 @@ void LayoutSystem::layoutSystemElements(const LayoutOptions& options, LayoutCont
|
|||
Rest* rest = toRest(item);
|
||||
Beam* beam = rest->beam();
|
||||
if (beam && !beam->cross()) {
|
||||
LayoutBeams::verticalAdjustBeamedRests(rest, beam);
|
||||
BeamLayout::verticalAdjustBeamedRests(rest, beam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -820,7 +820,7 @@ void LayoutSystem::layoutSystemElements(const LayoutOptions& options, LayoutCont
|
|||
// add beams to skline
|
||||
if (e->isChordRest()) {
|
||||
ChordRest* cr = toChordRest(e);
|
||||
if (LayoutBeams::isTopBeam(cr)) {
|
||||
if (BeamLayout::isTopBeam(cr)) {
|
||||
Beam* b = cr->beam();
|
||||
b->addSkyline(skyline);
|
||||
}
|
||||
|
|
|
@ -37,11 +37,14 @@
|
|||
|
||||
#include "../libmscore/bagpembell.h"
|
||||
#include "../libmscore/barline.h"
|
||||
#include "../libmscore/beam.h"
|
||||
|
||||
#include "../libmscore/note.h"
|
||||
|
||||
#include "../libmscore/staff.h"
|
||||
|
||||
#include "beamlayout.h"
|
||||
|
||||
using namespace mu::draw;
|
||||
using namespace mu::engraving;
|
||||
|
||||
|
@ -614,3 +617,13 @@ void TLayout::layout2(BarLine* item, LayoutContext&)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TLayout::layout(Beam* item, LayoutContext& ctx)
|
||||
{
|
||||
BeamLayout::layout(item, ctx);
|
||||
}
|
||||
|
||||
void TLayout::layout1(Beam* item, LayoutContext& ctx)
|
||||
{
|
||||
BeamLayout::layout1(item, ctx);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ class Articulation;
|
|||
|
||||
class BagpipeEmbellishment;
|
||||
class BarLine;
|
||||
class Beam;
|
||||
|
||||
class TLayout
|
||||
{
|
||||
|
@ -47,6 +48,8 @@ public:
|
|||
static void layout(BagpipeEmbellishment* item, LayoutContext& ctx);
|
||||
static void layout(BarLine* item, LayoutContext& ctx);
|
||||
static void layout2(BarLine* item, LayoutContext& ctx);
|
||||
static void layout(Beam* item, LayoutContext& ctx);
|
||||
static void layout1(Beam* item, LayoutContext& ctx);
|
||||
|
||||
private:
|
||||
static void layoutSingleGlyphAccidental(Accidental* item, LayoutContext& ctx);
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "realfn.h"
|
||||
|
||||
#include "draw/types/brush.h"
|
||||
#include "layout/tlayout.h"
|
||||
|
||||
#include "actionicon.h"
|
||||
#include "chord.h"
|
||||
|
@ -49,7 +50,7 @@
|
|||
#include "tremolo.h"
|
||||
#include "tuplet.h"
|
||||
|
||||
#include "layout/layoutbeams.h"
|
||||
#include "layout/beamlayout.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
|
@ -270,164 +271,6 @@ void Beam::move(const PointF& offset)
|
|||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// layout1
|
||||
// - remove beam segments
|
||||
// - detach from system
|
||||
// - calculate stem direction and set chord
|
||||
//---------------------------------------------------------
|
||||
|
||||
void Beam::layout1()
|
||||
{
|
||||
resetExplicitParent(); // parent is System
|
||||
|
||||
const StaffType* staffType = this->staffType();
|
||||
_tab = (staffType && staffType->isTabStaff()) ? staffType : nullptr;
|
||||
_isBesideTabStaff = _tab && !_tab->stemless() && !_tab->stemThrough();
|
||||
|
||||
// TAB's with stem beside staves have special layout
|
||||
if (_isBesideTabStaff) {
|
||||
_up = !_tab->stemsDown();
|
||||
_slope = 0.0;
|
||||
_cross = false;
|
||||
_minMove = 0;
|
||||
_maxMove = 0;
|
||||
for (ChordRest* cr : _elements) {
|
||||
if (cr->isChord()) {
|
||||
_up = cr->up();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (staff()->isDrumStaff(Fraction(0, 1))) {
|
||||
if (_direction != DirectionV::AUTO) {
|
||||
_up = _direction == DirectionV::UP;
|
||||
} else if (_isGrace) {
|
||||
_up = true;
|
||||
} else {
|
||||
// search through the beam for the first chord with explicit stem direction and use that.
|
||||
// if there is no explicit stem direction, default to the direction of the first stem.
|
||||
bool firstUp = false;
|
||||
bool firstChord = true;
|
||||
for (ChordRest* cr :_elements) {
|
||||
if (cr->isChord()) {
|
||||
DirectionV crDirection = toChord(cr)->stemDirection();
|
||||
if (crDirection != DirectionV::AUTO) {
|
||||
_up = crDirection == DirectionV::UP;
|
||||
break;
|
||||
} else if (firstChord) {
|
||||
firstUp = cr->up();
|
||||
firstChord = false;
|
||||
}
|
||||
}
|
||||
_up = firstUp;
|
||||
}
|
||||
}
|
||||
for (ChordRest* cr : _elements) {
|
||||
cr->computeUp();
|
||||
if (cr->isChord()) {
|
||||
toChord(cr)->layoutStem();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_minMove = std::numeric_limits<int>::max();
|
||||
_maxMove = std::numeric_limits<int>::min();
|
||||
double mag = 0.0;
|
||||
|
||||
_notes.clear();
|
||||
staff_idx_t staffIdx = mu::nidx;
|
||||
for (ChordRest* cr : _elements) {
|
||||
double m = cr->isSmall() ? score()->styleD(Sid::smallNoteMag) : 1.0;
|
||||
mag = std::max(mag, m);
|
||||
if (cr->isChord()) {
|
||||
Chord* chord = toChord(cr);
|
||||
staffIdx = chord->vStaffIdx();
|
||||
int i = chord->staffMove();
|
||||
_minMove = std::min(_minMove, i);
|
||||
_maxMove = std::max(_maxMove, i);
|
||||
|
||||
for (int distance : chord->noteDistances()) {
|
||||
_notes.push_back(distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(_notes.begin(), _notes.end());
|
||||
setMag(mag);
|
||||
|
||||
//
|
||||
// determine beam stem direction
|
||||
//
|
||||
if (_elements.empty()) {
|
||||
return;
|
||||
}
|
||||
ChordRest* firstNote = _elements.front();
|
||||
Measure* measure = firstNote->measure();
|
||||
bool hasMultipleVoices = measure->hasVoices(firstNote->staffIdx(), tick(), ticks());
|
||||
if (_direction != DirectionV::AUTO) {
|
||||
_up = _direction == DirectionV::UP;
|
||||
} else if (_maxMove > 0) {
|
||||
_up = false;
|
||||
} else if (_minMove < 0) {
|
||||
_up = true;
|
||||
} else if (_isGrace) {
|
||||
if (hasMultipleVoices) {
|
||||
_up = firstNote->track() % 2 == 0;
|
||||
} else {
|
||||
_up = true;
|
||||
}
|
||||
} else if (_notes.size()) {
|
||||
if (hasMultipleVoices) {
|
||||
_up = firstNote->track() % 2 == 0;
|
||||
} else {
|
||||
if (const Chord* chord = findChordWithCustomStemDirection()) {
|
||||
_up = chord->stemDirection() == DirectionV::UP;
|
||||
} else {
|
||||
std::set<int> noteSet(_notes.begin(), _notes.end());
|
||||
std::vector<int> notes(noteSet.begin(), noteSet.end());
|
||||
_up = Chord::computeAutoStemDirection(notes) > 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_up = true;
|
||||
}
|
||||
|
||||
int middleStaffLine = firstNote->staffType()->middleLine();
|
||||
for (size_t i = 0; i < _notes.size(); i++) {
|
||||
_notes[i] += middleStaffLine;
|
||||
}
|
||||
|
||||
_cross = _minMove != _maxMove;
|
||||
bool isEntirelyMoved = false;
|
||||
if (_minMove == _maxMove && _minMove != 0) {
|
||||
isEntirelyMoved = true;
|
||||
setStaffIdx(staffIdx);
|
||||
if (_direction == DirectionV::AUTO) {
|
||||
_up = _maxMove > 0;
|
||||
}
|
||||
} else if (_elements.size()) {
|
||||
setStaffIdx(_elements.at(0)->staffIdx());
|
||||
}
|
||||
|
||||
_slope = 0.0;
|
||||
|
||||
for (ChordRest* cr : _elements) {
|
||||
const bool staffMove = cr->isChord() ? toChord(cr)->staffMove() : false;
|
||||
if (!_cross || !staffMove) {
|
||||
if (cr->up() != _up) {
|
||||
cr->setUp(isEntirelyMoved ? _up : (_up != staffMove));
|
||||
if (cr->isChord()) {
|
||||
toChord(cr)->layoutStem();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// layout
|
||||
// TODO - document what this function does
|
||||
|
@ -435,62 +278,8 @@ void Beam::layout1()
|
|||
|
||||
void Beam::layout()
|
||||
{
|
||||
// all of the beam layout code depends on _elements being in order by tick
|
||||
// this may not be the case if two cr's were recently swapped.
|
||||
std::sort(_elements.begin(), _elements.end(),
|
||||
[](const ChordRest* a, const ChordRest* b) -> bool {
|
||||
return a->tick() < b->tick();
|
||||
});
|
||||
System* system = _elements.front()->measure()->system();
|
||||
setParent(system);
|
||||
|
||||
std::vector<ChordRest*> crl;
|
||||
|
||||
size_t n = 0;
|
||||
for (ChordRest* cr : _elements) {
|
||||
auto newSystem = cr->measure()->system();
|
||||
if (newSystem && newSystem != system) {
|
||||
SpannerSegmentType st;
|
||||
if (n == 0) {
|
||||
st = SpannerSegmentType::BEGIN;
|
||||
} else {
|
||||
st = SpannerSegmentType::MIDDLE;
|
||||
}
|
||||
++n;
|
||||
if (fragments.size() < n) {
|
||||
fragments.push_back(new BeamFragment);
|
||||
}
|
||||
layout2(crl, st, static_cast<int>(n) - 1);
|
||||
crl.clear();
|
||||
system = cr->measure()->system();
|
||||
}
|
||||
crl.push_back(cr);
|
||||
}
|
||||
setbbox(RectF());
|
||||
if (!crl.empty()) {
|
||||
SpannerSegmentType st;
|
||||
if (n == 0) {
|
||||
st = SpannerSegmentType::SINGLE;
|
||||
} else {
|
||||
st = SpannerSegmentType::END;
|
||||
}
|
||||
if (fragments.size() < (n + 1)) {
|
||||
fragments.push_back(new BeamFragment);
|
||||
}
|
||||
layout2(crl, st, static_cast<int>(n));
|
||||
|
||||
double lw2 = _beamWidth / 2.0;
|
||||
|
||||
for (const BeamSegment* bs : _beamSegments) {
|
||||
PolygonF a(4);
|
||||
a[0] = PointF(bs->line.x1(), bs->line.y1());
|
||||
a[1] = PointF(bs->line.x2(), bs->line.y2());
|
||||
a[2] = PointF(bs->line.x2(), bs->line.y2());
|
||||
a[3] = PointF(bs->line.x1(), bs->line.y1());
|
||||
RectF r(a.boundingRect().adjusted(0.0, -lw2, 0.0, lw2));
|
||||
addbbox(r);
|
||||
}
|
||||
}
|
||||
LayoutContext ctx(score());
|
||||
TLayout::layout(this, ctx);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
|
@ -557,279 +346,6 @@ void Beam::setTremAnchors()
|
|||
}
|
||||
}
|
||||
|
||||
void Beam::createBeamSegment(ChordRest* startCr, ChordRest* endCr, int level)
|
||||
{
|
||||
const bool isFirstSubgroup = startCr == _elements.front();
|
||||
const bool isLastSubgroup = endCr == _elements.back();
|
||||
const bool firstUp = startCr->up();
|
||||
const bool lastUp = endCr->up();
|
||||
bool overallUp = _up;
|
||||
if (isFirstSubgroup == isLastSubgroup) {
|
||||
// this subgroup is either the only one in the beam, or in the middle
|
||||
if (firstUp == lastUp) {
|
||||
// the "outside notes" of this subgroup go the same direction so use them
|
||||
// to determine the side of the beams
|
||||
overallUp = firstUp;
|
||||
} else {
|
||||
// no perfect way to solve this problem, for now we'll base it on the number of
|
||||
// up and down stemmed notes in this subgroup
|
||||
int upStems, downStems;
|
||||
upStems = downStems = 0;
|
||||
for (ChordRest* cr : _elements) {
|
||||
if (!cr->isChord() || cr->tick() < startCr->tick()) {
|
||||
continue;
|
||||
}
|
||||
if (cr->tick() > endCr->tick()) {
|
||||
break;
|
||||
}
|
||||
|
||||
++(toChord(cr)->up() ? upStems : downStems);
|
||||
|
||||
if (cr == endCr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (upStems == downStems) {
|
||||
// we are officially bamboozled. for now we can just use the default
|
||||
// direction based on the staff we're on I guess
|
||||
overallUp = _up;
|
||||
} else {
|
||||
// use the direction with the most stems
|
||||
overallUp = upStems > downStems;
|
||||
}
|
||||
}
|
||||
} else if (isFirstSubgroup) {
|
||||
overallUp = lastUp;
|
||||
} else if (isLastSubgroup) {
|
||||
overallUp = firstUp;
|
||||
}
|
||||
|
||||
const double startX = _layoutInfo.chordBeamAnchorX(startCr, BeamTremoloLayout::ChordBeamAnchorType::Start);
|
||||
const double endX = _layoutInfo.chordBeamAnchorX(endCr, BeamTremoloLayout::ChordBeamAnchorType::End);
|
||||
|
||||
double startY = _slope * (startX - _startAnchor.x()) + _startAnchor.y() - pagePos().y();
|
||||
double endY = _slope * (endX - _startAnchor.x()) + _startAnchor.y() - pagePos().y();
|
||||
|
||||
int beamsBelow = 0; // how many beams below level 0?
|
||||
int beamsAbove = 0; // how many beams above level 0?
|
||||
|
||||
// avoid adjusting for beams on opposite side of level 0
|
||||
if (level != 0) {
|
||||
for (const BeamSegment* beam : _beamSegments) {
|
||||
if (beam->level == 0 || beam->endTick < startCr->tick() || beam->startTick > endCr->tick()) {
|
||||
continue;
|
||||
}
|
||||
++(beam->above ? beamsAbove : beamsBelow);
|
||||
}
|
||||
|
||||
const int upValue = overallUp ? -1 : 1;
|
||||
const int extraBeamAdjust = overallUp ? beamsAbove : beamsBelow;
|
||||
const double verticalOffset = _beamDist * (level - extraBeamAdjust) * upValue;
|
||||
|
||||
if (RealIsEqual(_grow1, _grow2)) {
|
||||
startY -= verticalOffset * _grow1;
|
||||
endY -= verticalOffset * _grow1;
|
||||
} else {
|
||||
// Feathered beams
|
||||
double startProportionAlongX = (startX - _startAnchor.x()) / (_endAnchor.x() - _startAnchor.x());
|
||||
double endProportionAlongX = (endX - _startAnchor.x()) / (_endAnchor.x() - _startAnchor.x());
|
||||
|
||||
double grow1 = startProportionAlongX * (_grow2 - _grow1) + _grow1;
|
||||
double grow2 = endProportionAlongX * (_grow2 - _grow1) + _grow1;
|
||||
|
||||
startY -= verticalOffset * grow1;
|
||||
endY -= verticalOffset * grow2;
|
||||
}
|
||||
}
|
||||
|
||||
BeamSegment* b = new BeamSegment(this);
|
||||
b->above = !overallUp;
|
||||
b->level = level;
|
||||
b->line = LineF(startX, startY, endX, endY);
|
||||
b->startTick = startCr->tick();
|
||||
b->endTick = endCr->tick();
|
||||
_beamSegments.push_back(b);
|
||||
|
||||
if (level > 0) {
|
||||
++(b->above ? beamsAbove : beamsBelow);
|
||||
}
|
||||
|
||||
// extend stems properly
|
||||
for (ChordRest* cr : _elements) {
|
||||
if (!cr->isChord() || cr->tick() < startCr->tick()) {
|
||||
continue;
|
||||
}
|
||||
if (cr->tick() > endCr->tick()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Chord* chord = toChord(cr);
|
||||
double addition = 0.0;
|
||||
|
||||
if (level > 0) {
|
||||
double grow = _grow1;
|
||||
if (!RealIsEqual(_grow1, _grow2)) {
|
||||
double anchorX = _layoutInfo.chordBeamAnchorX(chord, BeamTremoloLayout::ChordBeamAnchorType::Middle);
|
||||
double proportionAlongX = (anchorX - _startAnchor.x()) / (_endAnchor.x() - _startAnchor.x());
|
||||
grow = proportionAlongX * (_grow2 - _grow1) + _grow1;
|
||||
}
|
||||
|
||||
int extraBeamAdjust = cr->up() ? beamsBelow : beamsAbove;
|
||||
addition = grow * (level - extraBeamAdjust) * _beamDist;
|
||||
}
|
||||
|
||||
if (level == 0 || !RealIsEqual(addition, 0.0)) {
|
||||
_layoutInfo.extendStem(chord, addition);
|
||||
}
|
||||
|
||||
if (chord == endCr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Beam::calcIsBeamletBefore(Chord* chord, int i, int level, bool isAfter32Break, bool isAfter64Break) const
|
||||
{
|
||||
// if first or last chord in beam group
|
||||
if (i == 0) {
|
||||
return false;
|
||||
} else if (i == static_cast<int>(_elements.size()) - 1) {
|
||||
return true;
|
||||
}
|
||||
// if first or last chord in tuplet
|
||||
Tuplet* tuplet = chord->tuplet();
|
||||
if (tuplet && chord == tuplet->elements().front()) {
|
||||
return false;
|
||||
} else if (tuplet && chord == tuplet->elements().back()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// next note has a beam break
|
||||
ChordRest* nextChordRest = _elements[i + 1];
|
||||
ChordRest* currChordRest = _elements[i];
|
||||
ChordRest* prevChordRest = _elements[i - 1];
|
||||
if (nextChordRest->isChord()) {
|
||||
bool nextBreak32 = false;
|
||||
bool nextBreak64 = false;
|
||||
bool currBreak32 = false;
|
||||
bool currBreak64 = false;
|
||||
calcBeamBreaks(currChordRest, prevChordRest, prevChordRest->beams(), currBreak32, currBreak64);
|
||||
calcBeamBreaks(nextChordRest, currChordRest, level, nextBreak32, nextBreak64);
|
||||
if ((nextBreak32 && level >= 1) || (!currBreak32 && nextBreak64 && level >= 2)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if previous or next chord has more beams, point in that direction
|
||||
int previousChordLevel = -1;
|
||||
int nextChordLevel = -1;
|
||||
int previousOffset = 1;
|
||||
while (i - previousOffset >= 0) {
|
||||
ChordRest* previous = _elements[i - previousOffset];
|
||||
if (previous->isChord()) {
|
||||
previousChordLevel = toChord(previous)->beams();
|
||||
if (isAfter32Break) {
|
||||
previousChordLevel = std::min(previousChordLevel, 1);
|
||||
} else if (isAfter64Break) {
|
||||
previousChordLevel = std::min(previousChordLevel, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
++previousOffset;
|
||||
}
|
||||
|
||||
int nextOffset = 1;
|
||||
while (i + nextOffset < static_cast<int>(_elements.size())) {
|
||||
ChordRest* next = _elements[i + nextOffset];
|
||||
if (next->isChord()) {
|
||||
nextChordLevel = toChord(next)->beams();
|
||||
break;
|
||||
}
|
||||
++nextOffset;
|
||||
}
|
||||
int chordLevelDifference = nextChordLevel - previousChordLevel;
|
||||
if (chordLevelDifference != 0) {
|
||||
return chordLevelDifference < 0;
|
||||
}
|
||||
|
||||
// if the chord ends a subdivision of the beat
|
||||
Fraction baseTick = tuplet ? tuplet->tick() : chord->measure()->tick();
|
||||
Fraction tickNext = nextChordRest->tick() - baseTick;
|
||||
if (tuplet) {
|
||||
// for tuplets with odd ratios, apply ratio
|
||||
// for tuplets with even ratios, use actual beat
|
||||
Fraction ratio = tuplet->ratio();
|
||||
if (ratio.numerator() & 1) {
|
||||
tickNext *= ratio;
|
||||
}
|
||||
}
|
||||
|
||||
static const int BEAM_TUPLET_TOLERANCE = 6;
|
||||
int tickLargeSize = chord->ticks().ticks() * 2;
|
||||
int remainder = tickNext.ticks() % tickLargeSize;
|
||||
if (remainder <= BEAM_TUPLET_TOLERANCE || (tickLargeSize - remainder) <= BEAM_TUPLET_TOLERANCE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// default case
|
||||
return false;
|
||||
}
|
||||
|
||||
void Beam::createBeamletSegment(ChordRest* cr, bool isBefore, int level)
|
||||
{
|
||||
const double startX = _layoutInfo.chordBeamAnchorX(cr,
|
||||
isBefore ? BeamTremoloLayout::ChordBeamAnchorType::End : BeamTremoloLayout::ChordBeamAnchorType::Start);
|
||||
|
||||
const double beamletLength = score()->styleMM(Sid::beamMinLen).val()
|
||||
* cr->mag();
|
||||
|
||||
const double endX = startX + (isBefore ? -beamletLength : beamletLength);
|
||||
|
||||
double startY = _slope * (startX - _startAnchor.x()) + _startAnchor.y() - pagePos().y();
|
||||
double endY = _slope * (endX - startX) + startY;
|
||||
|
||||
// how many beams past level 0 (i.e. beams on the other side of level 0 for this subgroup)
|
||||
int extraBeamAdjust = 0;
|
||||
|
||||
// avoid adjusting for beams on opposite side of level 0
|
||||
for (const BeamSegment* beam : _beamSegments) {
|
||||
if (beam->level == 0 || beam->line.x2() < startX || beam->line.x1() > endX) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cr->up() == beam->above) {
|
||||
extraBeamAdjust++;
|
||||
}
|
||||
}
|
||||
|
||||
const int upValue = cr->up() ? -1 : 1;
|
||||
const double verticalOffset = _beamDist * (level - extraBeamAdjust) * upValue;
|
||||
|
||||
if (RealIsEqual(_grow1, _grow2)) {
|
||||
startY -= verticalOffset * _grow1;
|
||||
endY -= verticalOffset * _grow1;
|
||||
} else {
|
||||
// Feathered beams
|
||||
double startProportionAlongX = (startX - _startAnchor.x()) / (_endAnchor.x() - _startAnchor.x());
|
||||
double endProportionAlongX = (endX - _startAnchor.x()) / (_endAnchor.x() - _startAnchor.x());
|
||||
|
||||
double grow1 = startProportionAlongX * (_grow2 - _grow1) + _grow1;
|
||||
double grow2 = endProportionAlongX * (_grow2 - _grow1) + _grow1;
|
||||
|
||||
startY -= verticalOffset * grow1;
|
||||
endY -= verticalOffset * grow2;
|
||||
}
|
||||
|
||||
BeamSegment* b = new BeamSegment(this);
|
||||
b->above = !cr->up();
|
||||
b->level = level;
|
||||
b->line = LineF(startX, startY, endX, endY);
|
||||
b->isBeamlet = true;
|
||||
b->isBefore = isBefore;
|
||||
cr->setBeamlet(b);
|
||||
_beamSegments.push_back(b);
|
||||
}
|
||||
|
||||
void Beam::calcBeamBreaks(const ChordRest* chord, const ChordRest* prevChord, int level, bool& isBroken32, bool& isBroken64) const
|
||||
{
|
||||
BeamMode beamMode = chord->beamMode();
|
||||
|
@ -868,394 +384,6 @@ void Beam::calcBeamBreaks(const ChordRest* chord, const ChordRest* prevChord, in
|
|||
}
|
||||
}
|
||||
|
||||
void Beam::createBeamSegments(const std::vector<ChordRest*>& chordRests)
|
||||
{
|
||||
DeleteAll(_beamSegments);
|
||||
_beamSegments.clear();
|
||||
|
||||
bool levelHasBeam = false;
|
||||
int level = 0;
|
||||
do {
|
||||
levelHasBeam = false;
|
||||
ChordRest* startCr = nullptr;
|
||||
ChordRest* endCr = nullptr;
|
||||
bool breakBeam = false;
|
||||
bool previousBreak32 = false;
|
||||
bool previousBreak64 = false;
|
||||
|
||||
for (size_t i = 0; i < chordRests.size(); i++) {
|
||||
ChordRest* chordRest = chordRests[i];
|
||||
ChordRest* prevChordRest = i < 1 ? nullptr : chordRests[i - 1];
|
||||
|
||||
if (level < chordRest->beams()) {
|
||||
levelHasBeam = true;
|
||||
}
|
||||
bool isBroken32 = false;
|
||||
bool isBroken64 = false;
|
||||
// updates isBroken32 and isBroken64
|
||||
calcBeamBreaks(chordRest, prevChordRest, level, isBroken32, isBroken64);
|
||||
breakBeam = isBroken32 || isBroken64;
|
||||
|
||||
if (level < chordRest->beams() && !breakBeam) {
|
||||
endCr = chordRest;
|
||||
if (!startCr) {
|
||||
startCr = chordRest;
|
||||
}
|
||||
} else {
|
||||
if (startCr && endCr) {
|
||||
if (startCr == endCr && startCr->isChord()) {
|
||||
bool isBeamletBefore = calcIsBeamletBefore(toChord(
|
||||
startCr), static_cast<int>(i) - 1, level, previousBreak32,
|
||||
previousBreak64);
|
||||
createBeamletSegment(toChord(startCr), isBeamletBefore, level);
|
||||
} else {
|
||||
createBeamSegment(startCr, endCr, level);
|
||||
}
|
||||
}
|
||||
bool setCr = chordRest && chordRest->isChord() && breakBeam && level < chordRest->beams();
|
||||
startCr = setCr ? chordRest : nullptr;
|
||||
endCr = setCr ? chordRest : nullptr;
|
||||
}
|
||||
previousBreak32 = isBroken32;
|
||||
previousBreak64 = isBroken64;
|
||||
}
|
||||
|
||||
// if the beam ends on the last chord
|
||||
if (startCr && (endCr || breakBeam)) {
|
||||
if ((startCr == endCr || !endCr) && startCr->isChord()) {
|
||||
// this chord is either the last chord, or the first (followed by a rest)
|
||||
bool isBefore = !(startCr == chordRests.front());
|
||||
createBeamletSegment(toChord(startCr), isBefore, level);
|
||||
} else {
|
||||
createBeamSegment(startCr, endCr, level);
|
||||
}
|
||||
}
|
||||
level++;
|
||||
} while (levelHasBeam);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// layout2
|
||||
//---------------------------------------------------------
|
||||
|
||||
void Beam::layout2(const std::vector<ChordRest*>& chordRests, SpannerSegmentType, int frag)
|
||||
{
|
||||
_layoutInfo = BeamTremoloLayout(this);
|
||||
Chord* startChord = nullptr;
|
||||
Chord* endChord = nullptr;
|
||||
if (chordRests.empty()) {
|
||||
return;
|
||||
}
|
||||
for (auto chordRest : chordRests) {
|
||||
if (chordRest->isChord()) {
|
||||
if (!startChord) {
|
||||
startChord = toChord(chordRest);
|
||||
endChord = startChord;
|
||||
} else {
|
||||
endChord = toChord(chordRest);
|
||||
}
|
||||
toChord(chordRest)->layoutStem();
|
||||
}
|
||||
}
|
||||
if (!startChord) {
|
||||
// we were passed a vector of only rests. we don't support beams across only rests
|
||||
// this beam will be deleted in LayoutBeams
|
||||
return;
|
||||
}
|
||||
|
||||
_beamSpacing = score()->styleB(Sid::useWideBeams) ? 4 : 3;
|
||||
_beamDist = (_beamSpacing / 4.0) * spatium() * mag();
|
||||
_beamWidth = point(score()->styleS(Sid::beamWidth)) * mag();
|
||||
|
||||
_startAnchor = _layoutInfo.chordBeamAnchor(startChord, BeamTremoloLayout::ChordBeamAnchorType::Start);
|
||||
_endAnchor = _layoutInfo.chordBeamAnchor(endChord, BeamTremoloLayout::ChordBeamAnchorType::End);
|
||||
|
||||
if (_isGrace) {
|
||||
_beamDist *= score()->styleD(Sid::graceNoteMag);
|
||||
_beamWidth *= score()->styleD(Sid::graceNoteMag);
|
||||
}
|
||||
|
||||
int fragmentIndex = (_direction == DirectionV::AUTO || _direction == DirectionV::DOWN) ? 0 : 1;
|
||||
if (_userModified[fragmentIndex]) {
|
||||
_layoutInfo = BeamTremoloLayout(this);
|
||||
double startY = fragments[frag]->py1[fragmentIndex];
|
||||
double endY = fragments[frag]->py2[fragmentIndex];
|
||||
if (score()->styleB(Sid::snapCustomBeamsToGrid)) {
|
||||
const double quarterSpace = spatium() / 4;
|
||||
startY = round(startY / quarterSpace) * quarterSpace;
|
||||
endY = round(endY / quarterSpace) * quarterSpace;
|
||||
}
|
||||
startY += pagePos().y();
|
||||
endY += pagePos().y();
|
||||
_startAnchor.setY(startY);
|
||||
_endAnchor.setY(endY);
|
||||
_layoutInfo.setAnchors(_startAnchor, _endAnchor);
|
||||
_slope = (_endAnchor.y() - _startAnchor.y()) / (_endAnchor.x() - _startAnchor.x());
|
||||
createBeamSegments(chordRests);
|
||||
setTremAnchors();
|
||||
return;
|
||||
}
|
||||
|
||||
// anchor represents the middle of the beam, not the tip of the stem
|
||||
// location depends on _isBesideTabStaff
|
||||
|
||||
if (!_isBesideTabStaff) {
|
||||
_layoutInfo = BeamTremoloLayout(this);
|
||||
_layoutInfo.calculateAnchors(chordRests, _notes);
|
||||
_startAnchor = _layoutInfo.startAnchor();
|
||||
_endAnchor = _layoutInfo.endAnchor();
|
||||
_slope = (_endAnchor.y() - _startAnchor.y()) / (_endAnchor.x() - _startAnchor.x());
|
||||
_beamDist = _layoutInfo.beamDist();
|
||||
} else {
|
||||
_slope = 0;
|
||||
Chord* startChord = nullptr;
|
||||
for (ChordRest* cr : chordRests) {
|
||||
if (cr->isChord()) {
|
||||
startChord = toChord(cr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_layoutInfo = BeamTremoloLayout(this);
|
||||
double x1 = _layoutInfo.chordBeamAnchorX(chordRests.front(), BeamTremoloLayout::ChordBeamAnchorType::Start);
|
||||
double x2 = _layoutInfo.chordBeamAnchorX(chordRests.back(), BeamTremoloLayout::ChordBeamAnchorType::End);
|
||||
double y = _layoutInfo.chordBeamAnchorY(startChord);
|
||||
_startAnchor = PointF(x1, y);
|
||||
_endAnchor = PointF(x2, y);
|
||||
_layoutInfo.setAnchors(_startAnchor, _endAnchor);
|
||||
_beamWidth = _layoutInfo.beamWidth();
|
||||
}
|
||||
|
||||
fragments[frag]->py1[fragmentIndex] = _startAnchor.y() - pagePos().y();
|
||||
fragments[frag]->py2[fragmentIndex] = _endAnchor.y() - pagePos().y();
|
||||
|
||||
createBeamSegments(chordRests);
|
||||
setTremAnchors();
|
||||
}
|
||||
|
||||
bool Beam::layout2Cross(const std::vector<ChordRest*>& chordRests, int frag)
|
||||
{
|
||||
int fragmentIndex = (_direction == DirectionV::AUTO || _direction == DirectionV::DOWN) ? 0 : 1;
|
||||
ChordRest* startCr = _elements.front();
|
||||
ChordRest* endCr = _elements.back();
|
||||
|
||||
const double quarterSpace = spatium() / 4;
|
||||
// imagine a line of beamed notes all in a row on the same staff. the first and last of those
|
||||
// are the 'outside' notes, and the slant of the beam is going to be affected by the 'middle' notes
|
||||
// between them.
|
||||
// we have to keep track of this for both staves.
|
||||
Chord* topFirst = nullptr;
|
||||
Chord* topLast = nullptr;
|
||||
Chord* bottomFirst = nullptr;
|
||||
Chord* bottomLast = nullptr;
|
||||
int maxMiddleTopLine = std::numeric_limits<int>::min(); // lowest note in the top staff
|
||||
int minMiddleBottomLine = std::numeric_limits<int>::max(); // highest note in the bottom staff
|
||||
int prevTopLine = maxMiddleTopLine; // previous note's line position (top)
|
||||
int prevBottomLine = minMiddleBottomLine; // previous note's line position (bottom)
|
||||
// if the immediate neighbor of one of the two 'outside' notes on either the top or bottom
|
||||
// are the same as that outside note, we need to record it so that we can add a 1/4 space slant.
|
||||
bool secondTopIsSame = false;
|
||||
bool secondBottomIsSame = false;
|
||||
bool penultimateTopIsSame = false;
|
||||
bool penultimateBottomIsSame = false;
|
||||
double maxY = std::numeric_limits<double>::max();
|
||||
double minY = std::numeric_limits<double>::min();
|
||||
int otherStaff = 0;
|
||||
// recompute _minMove and _maxMove as they may have shifted since last layout
|
||||
_minMove = std::numeric_limits<int>::max();
|
||||
_maxMove = std::numeric_limits<int>::min();
|
||||
for (ChordRest* c : chordRests) {
|
||||
IF_ASSERT_FAILED(c) {
|
||||
continue;
|
||||
}
|
||||
int staffMove = c->staffMove();
|
||||
_minMove = std::min(_minMove, staffMove);
|
||||
_maxMove = std::max(_maxMove, staffMove);
|
||||
|
||||
if (staffMove != 0) {
|
||||
otherStaff = staffMove;
|
||||
}
|
||||
}
|
||||
if (otherStaff == 0 || _minMove == _maxMove) {
|
||||
return false;
|
||||
}
|
||||
// Find the notes on the top and bottom of staves
|
||||
//
|
||||
bool checkNextTop = false;
|
||||
bool checkNextBottom = false;
|
||||
for (ChordRest* cr : chordRests) {
|
||||
if (!cr->isChord()) {
|
||||
continue;
|
||||
}
|
||||
Chord* c = toChord(cr);
|
||||
if ((c->staffMove() == otherStaff && otherStaff > 0) || (c->staffMove() != otherStaff && otherStaff < 0)) {
|
||||
// this chord is on the bottom staff
|
||||
if (penultimateBottomIsSame) {
|
||||
// the chord we took as the penultimate bottom note wasn't.
|
||||
// so treat it properly as a middle note
|
||||
minMiddleBottomLine = std::min(minMiddleBottomLine, prevBottomLine);
|
||||
penultimateBottomIsSame = false;
|
||||
}
|
||||
checkNextTop = false; // we are no longer looking for the second note in the top
|
||||
// staff being the same as the first--this note is on the bottom.
|
||||
if (!bottomFirst) {
|
||||
bottomFirst = c;
|
||||
checkNextBottom = true; // this was the first bottom note, so check for second next time
|
||||
} else {
|
||||
penultimateBottomIsSame = prevBottomLine == c->line();
|
||||
if (!penultimateBottomIsSame) {
|
||||
minMiddleBottomLine = std::min(minMiddleBottomLine, prevBottomLine);
|
||||
}
|
||||
if (checkNextBottom) {
|
||||
// this is the second bottom note, so we should see if this one is same line as first
|
||||
secondBottomIsSame = c->line() == bottomFirst->line();
|
||||
checkNextBottom = false;
|
||||
} else {
|
||||
prevBottomLine = c->line();
|
||||
}
|
||||
bottomLast = c;
|
||||
}
|
||||
maxY = std::min(maxY, _layoutInfo.chordBeamAnchorY(toChord(c)));
|
||||
} else {
|
||||
// this chord is on the top staff
|
||||
if (penultimateTopIsSame) {
|
||||
// the chord we took as the penultimate top note wasn't.
|
||||
// so treat it properly as a middle note
|
||||
maxMiddleTopLine = std::max(maxMiddleTopLine, prevTopLine);
|
||||
penultimateTopIsSame = false;
|
||||
}
|
||||
checkNextBottom = false; // no longer looking for a bottom second note since this is on top
|
||||
if (!topFirst) {
|
||||
topFirst = c;
|
||||
checkNextTop = true;
|
||||
} else {
|
||||
penultimateTopIsSame = prevTopLine == c->line();
|
||||
if (!penultimateTopIsSame) {
|
||||
maxMiddleTopLine = std::max(maxMiddleTopLine, prevTopLine);
|
||||
}
|
||||
if (checkNextTop) {
|
||||
secondTopIsSame = c->line() == topFirst->line();
|
||||
checkNextTop = false;
|
||||
} else {
|
||||
prevTopLine = c->line();
|
||||
}
|
||||
topLast = c;
|
||||
}
|
||||
minY = std::max(minY, _layoutInfo.chordBeamAnchorY(toChord(c)));
|
||||
}
|
||||
}
|
||||
_startAnchor.ry() = (maxY + minY) / 2;
|
||||
_endAnchor.ry() = (maxY + minY) / 2;
|
||||
_slope = 0;
|
||||
|
||||
if (!noSlope()) {
|
||||
int topFirstLine = topFirst ? topFirst->downNote()->line() : 0;
|
||||
int topLastLine = topLast ? topLast->downNote()->line() : 0;
|
||||
int bottomFirstLine = bottomFirst ? bottomFirst->upNote()->line() : 0;
|
||||
int bottomLastLine = bottomLast ? bottomLast->upNote()->line() : 0;
|
||||
bool constrainTopToQuarter = false;
|
||||
bool constrainBottomToQuarter = false;
|
||||
if ((topFirstLine > topLastLine && secondTopIsSame)
|
||||
|| (topFirstLine < topLastLine && penultimateTopIsSame)) {
|
||||
constrainTopToQuarter = true;
|
||||
}
|
||||
if ((bottomFirstLine < bottomLastLine && secondBottomIsSame)
|
||||
|| (bottomFirstLine > bottomLastLine && penultimateBottomIsSame)) {
|
||||
constrainBottomToQuarter = true;
|
||||
}
|
||||
if (!topLast && !bottomLast && topFirst && bottomFirst) {
|
||||
// if there are only two notes, one on each staff, special case
|
||||
// take max slope into account
|
||||
double yFirst, yLast;
|
||||
if (topFirst->tick() < bottomFirst->tick()) {
|
||||
yFirst = topFirst->stemPos().y();
|
||||
yLast = bottomFirst->stemPos().y();
|
||||
} else {
|
||||
yFirst = bottomFirst->stemPos().y();
|
||||
yLast = topFirst->stemPos().y();
|
||||
}
|
||||
int desiredSlant = round((yFirst - yLast) / spatium());
|
||||
int slant = std::min(std::abs(desiredSlant), _layoutInfo.getMaxSlope());
|
||||
slant *= (desiredSlant < 0) ? -quarterSpace : quarterSpace;
|
||||
_startAnchor.ry() += (slant / 2);
|
||||
_endAnchor.ry() -= (slant / 2);
|
||||
} else if (!topLast || !bottomLast) {
|
||||
// otherwise, if there is only one note on one of the staves, use slope from other staff
|
||||
int startNote = 0;
|
||||
int endNote = 0;
|
||||
bool forceHoriz = false;
|
||||
if (!topLast) {
|
||||
startNote = bottomFirstLine;
|
||||
endNote = bottomLastLine;
|
||||
if (minMiddleBottomLine <= std::min(startNote, endNote)) {
|
||||
// there is a note closer to the beam than the start and end notes
|
||||
// we force horizontal beam here.
|
||||
forceHoriz = true;
|
||||
}
|
||||
} else if (!bottomLast) {
|
||||
startNote = topFirstLine;
|
||||
endNote = topLastLine;
|
||||
if (maxMiddleTopLine >= std::max(startNote, endNote)) {
|
||||
// same as above, for the top staff
|
||||
// force horizontal.
|
||||
forceHoriz = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!forceHoriz) {
|
||||
int slant = startNote - endNote;
|
||||
slant = std::min(std::abs(slant), _layoutInfo.getMaxSlope());
|
||||
if ((!bottomLast && constrainTopToQuarter) || (!topLast && constrainBottomToQuarter)) {
|
||||
slant = 1;
|
||||
}
|
||||
double slope = slant * (startNote > endNote ? quarterSpace : -quarterSpace);
|
||||
_startAnchor.ry() += (slope / 2);
|
||||
_endAnchor.ry() -= (slope / 2);
|
||||
} // otherwise, do nothing, beam is already horizontal.
|
||||
} else {
|
||||
// otherwise, there are at least two notes on each staff
|
||||
// (that is, topLast and bottomLast are both set)
|
||||
bool forceHoriz = false;
|
||||
if (topFirstLine == topLastLine || bottomFirstLine == bottomLastLine) {
|
||||
// if outside notes on top or bottom staff are on the same staff line, slope = 0
|
||||
// no further adjustment needed, the beam is already well-placed and horizontal
|
||||
forceHoriz = true;
|
||||
}
|
||||
// otherwise, we have to compare the slopes from the top staff and bottom staff.
|
||||
int topSlant = topFirstLine - topLastLine;
|
||||
if (constrainTopToQuarter && topSlant != 0) {
|
||||
topSlant = topFirstLine < topLastLine ? -1 : 1;
|
||||
}
|
||||
int bottomSlant = bottomFirstLine - bottomLastLine;
|
||||
if (constrainBottomToQuarter && bottomSlant != 0) {
|
||||
bottomSlant = bottomFirstLine < bottomLastLine ? -1 : 1;
|
||||
}
|
||||
if ((maxMiddleTopLine >= std::max(topFirstLine, topLastLine)
|
||||
|| (minMiddleBottomLine <= std::min(bottomFirstLine, bottomLastLine)))) {
|
||||
forceHoriz = true;
|
||||
}
|
||||
if (topSlant == 0 || bottomSlant == 0 || forceHoriz) {
|
||||
// if one of the slants is 0, the whole slant is zero
|
||||
} else if ((topSlant < 0 && bottomSlant < 0) || (topSlant > 0 && bottomSlant > 0)) {
|
||||
int slant = (abs(topSlant) < abs(bottomSlant)) ? topSlant : bottomSlant;
|
||||
slant = std::min(std::abs(slant), _layoutInfo.getMaxSlope());
|
||||
double slope = slant * ((topSlant < 0) ? -quarterSpace : quarterSpace);
|
||||
_startAnchor.ry() += (slope / 2);
|
||||
_endAnchor.ry() -= (slope / 2);
|
||||
} else {
|
||||
// if the two slopes are in opposite directions, flat!
|
||||
// nothing needs to be done, the beam is already horizontal and placed nicely
|
||||
}
|
||||
}
|
||||
_startAnchor.setX(_layoutInfo.chordBeamAnchorX(startCr, BeamTremoloLayout::ChordBeamAnchorType::Start));
|
||||
_endAnchor.setX(_layoutInfo.chordBeamAnchorX(endCr, BeamTremoloLayout::ChordBeamAnchorType::End));
|
||||
_slope = (_endAnchor.y() - _startAnchor.y()) / (_endAnchor.x() - _startAnchor.x());
|
||||
}
|
||||
fragments[frag]->py1[fragmentIndex] = _startAnchor.y() - pagePos().y();
|
||||
fragments[frag]->py2[fragmentIndex] = _endAnchor.y() - pagePos().y();
|
||||
createBeamSegments(chordRests);
|
||||
return true;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// spatiumChanged
|
||||
//---------------------------------------------------------
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#ifndef __BEAM_H__
|
||||
#define __BEAM_H__
|
||||
|
||||
#include "beamtremololayout.h"
|
||||
#include "layout/beamtremololayout.h"
|
||||
#include "engravingitem.h"
|
||||
#include "durationtype.h"
|
||||
#include "property.h"
|
||||
|
@ -84,6 +84,8 @@ class Beam final : public EngravingItem
|
|||
OBJECT_ALLOCATOR(engraving, Beam)
|
||||
DECLARE_CLASSOF(ElementType::BEAM)
|
||||
|
||||
friend class BeamLayout;
|
||||
|
||||
std::vector<ChordRest*> _elements; // must be sorted by tick
|
||||
std::vector<BeamSegment*> _beamSegments;
|
||||
DirectionV _direction { DirectionV::AUTO };
|
||||
|
@ -125,12 +127,6 @@ class Beam final : public EngravingItem
|
|||
Beam(System* parent);
|
||||
Beam(const Beam&);
|
||||
|
||||
bool calcIsBeamletBefore(Chord* chord, int i, int level, bool isAfter32Break, bool isAfter64Break) const;
|
||||
void createBeamSegment(ChordRest* startChord, ChordRest* endChord, int level);
|
||||
void createBeamletSegment(ChordRest* chord, bool isBefore, int level);
|
||||
void createBeamSegments(const std::vector<ChordRest*>& chordRests);
|
||||
void layout2(const std::vector<ChordRest*>& chordRests, SpannerSegmentType, int frag);
|
||||
bool layout2Cross(const std::vector<ChordRest*>& chordRests, int frag);
|
||||
void addChordRest(ChordRest* a);
|
||||
void removeChordRest(ChordRest* a);
|
||||
|
||||
|
@ -162,7 +158,6 @@ public:
|
|||
|
||||
System* system() const { return toSystem(explicitParent()); }
|
||||
|
||||
void layout1();
|
||||
void layout() override;
|
||||
void layoutIfNeed();
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include "style/style.h"
|
||||
#include "types/typesconv.h"
|
||||
#include "layout/tlayout.h"
|
||||
|
||||
#include "actionicon.h"
|
||||
#include "articulation.h"
|
||||
|
@ -571,7 +572,8 @@ void ChordRest::removeDeleteBeam(bool beamed)
|
|||
if (b->empty()) {
|
||||
score()->undoRemoveElement(b);
|
||||
} else {
|
||||
b->layout1();
|
||||
LayoutContext ctx(score());
|
||||
TLayout::layout1(b, ctx);
|
||||
}
|
||||
}
|
||||
if (!beamed && isChord()) {
|
||||
|
|
|
@ -37,8 +37,6 @@ set(LIBMSCORE_SRC
|
|||
${CMAKE_CURRENT_LIST_DIR}/barline.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/beam.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/beam.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/beamtremololayout.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/beamtremololayout.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/bend.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/bend.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/box.cpp
|
||||
|
|
|
@ -26,19 +26,12 @@
|
|||
#include <deque>
|
||||
|
||||
#include "draw/types/color.h"
|
||||
#include "types/types.h"
|
||||
#include "engravingitem.h"
|
||||
|
||||
namespace mu::engraving {
|
||||
class Spanner;
|
||||
|
||||
//---------------------------------------------------------
|
||||
// SpannerSegmentType
|
||||
//---------------------------------------------------------
|
||||
|
||||
enum class SpannerSegmentType {
|
||||
SINGLE, BEGIN, MIDDLE, END
|
||||
};
|
||||
|
||||
//---------------------------------------------------------
|
||||
// @@ SpannerSegment
|
||||
//! parent: System
|
||||
|
|
|
@ -889,6 +889,10 @@ enum class LyricsSyllabic : char {
|
|||
SINGLE, BEGIN, END, MIDDLE
|
||||
};
|
||||
|
||||
enum class SpannerSegmentType {
|
||||
SINGLE, BEGIN, MIDDLE, END
|
||||
};
|
||||
|
||||
//---------------------------------------------------------
|
||||
// Key
|
||||
//---------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue