grace notes code restructure

This commit is contained in:
Michele Spagnolo 2022-05-24 11:14:03 +02:00 committed by pereverzev+v
parent 061527a7ee
commit 74ac594483
11 changed files with 154 additions and 106 deletions

View file

@ -1165,20 +1165,17 @@ void LayoutChords::layoutChords3(const MStyle& style, std::vector<Note*>& notes,
/* updateGraceNotes()
* Processes a full measure, making sure that all grace notes are
* attacched to the correct segment.
* attacched to the correct segment. Has to be performed after
* all the segments are known.
* */
void LayoutChords::updateGraceNotes(Measure* measure)
{
Score* score = measure->score();
for (Segment& s : measure->segments()) { // Clean everything
for (unsigned track = 0; track < score->staves().size() * VOICES; ++track) {
if (!s.graceNotesBefore(track).empty()) {
s.graceNotesBefore(track).clear();
s.createShape(track2staff(track));
}
if (!s.graceNotesAfter(track).empty()) {
s.graceNotesAfter(track).clear();
s.createShape(track2staff(track));
EngravingItem* item = s.preAppendedItem(track);
if (item && item->isGraceNotesGroup()) {
s.clearPreAppended(track);
}
}
}
@ -1189,39 +1186,65 @@ void LayoutChords::updateGraceNotes(Measure* measure)
}
for (auto el : s.elist()) {
if (el && el->isChord()) {
toChord(el)->attachGraceNotes();
appendGraceNotes(toChord(el));
}
}
}
}
/* layoutGraceNotes()
* Needs to be called when creating the segment shape. Make sure it is called *last*,
* so that grace notes can be correctly padded against the segment shape.*/
void LayoutChords::layoutGraceNotes(Segment* seg, int staffIdx)
void LayoutChords::appendGraceNotes(Chord* chord)
{
Score* score = seg->score();
seg->createShape(staffIdx);
Shape& segShape = seg->staffShape(staffIdx);
int startTrack = staff2track(staffIdx, 0);
int endTrack = startTrack + VOICES;
for (int track = startTrack; track < endTrack; ++track) {
if (seg->graceNotesBefore(track).empty() && seg->graceNotesAfter(track).empty()) {
continue;
Segment* segment = chord->segment();
Measure* measure = chord->measure();
int track = chord->track();
int staffIdx = chord->staffIdx();
GraceNotesGroup& gnb = chord->graceNotesBefore();
GraceNotesGroup& gna = chord->graceNotesAfter();
//Attach graceNotesBefore of this chord to *this* segment
if (!gnb.empty()) {
// If this segment already contains grace notes in the same voice (could happen if a
// previous chord has appended grace-notes-after here) put them in the same vector.
EngravingItem* item = segment->preAppendedItem(track);
if (item && item->isGraceNotesGroup()) {
GraceNotesGroup* gng = toGraceNotesGroup(item);
gng->insert(gng->end(), gnb.begin(), gnb.end());
} else {
segment->preAppend(&gnb, track);
}
if (!score->staves().at(staffIdx)->visible()) {
continue;
segment->createShape(staffIdx);
}
//Attach graceNotesAfter of this chord to the *following* segment
if (!gna.empty()) {
Segment* followingSeg = measure->tick2segment(segment->tick() + chord->actualTicks(), SegmentType::All);
while (followingSeg && !followingSeg->hasElements(staff2track(staffIdx), staff2track(staffIdx) + 3)) {
// If there is nothing on this staff, go to next segment
followingSeg = followingSeg->next();
}
// Collect grace-after and grace-before in a single vector
std::vector<Chord*> gnCollector = seg->graceNotesAfter(track);
gnCollector.insert(gnCollector.end(), seg->graceNotesBefore(track).begin(), seg->graceNotesBefore(track).end());
// Compute grace notes spacing
for (auto gn = gnCollector.rbegin(); gn != gnCollector.rend(); ++gn) { // Start layout from the last note
Chord* GN = *gn;
double offset = -GN->shape().minHorizontalDistance(segShape, score); // Pad the grace note against the segment shape
segShape.add(GN->shape().translated(mu::PointF(offset, 0.0))); // Add grace note shape to segment shape
double xpos = offset - GN->parentItem()->rxoffset() - GN->parentItem()->rxpos();
GN->setPos(xpos, 0); // Set grace note position
if (followingSeg) {
followingSeg->preAppend(&gna, track);
followingSeg->createShape(staffIdx);
}
}
}
/* Grace-notes-after have the special property of belonging to
* a segment but being pre-appended to another. This repositioning
* is needed and must be called AFTER horizontal spacing is calculated. */
void LayoutChords::repositionGraceNotesAfter(Segment* segment)
{
int tracks = segment->score()->staves().size() * VOICES;
for (int track = 0; track < tracks; track++) {
EngravingItem* item = segment->preAppendedItem(track);
if (!item || !item->isGraceNotesGroup()) {
continue;
}
GraceNotesGroup* gng = toGraceNotesGroup(item);
for (Chord* chord : *gng) {
double offset = segment->rxpos() - chord->parentItem()->parentItem()->rxpos();
// Difference between the segment they "belong" and the segment they are "appended" to.
chord->setPos(chord->pos().x() + offset, 0.0);
}
}
}

View file

@ -34,6 +34,7 @@ class Note;
class Staff;
class MStyle;
class Measure;
class Chord;
}
namespace mu::engraving {
@ -45,7 +46,8 @@ public:
static qreal layoutChords2(std::vector<Ms::Note*>& notes, bool up);
static void layoutChords3(const Ms::MStyle& style, std::vector<Ms::Note*>&, const Ms::Staff*, Ms::Segment*);
static void updateGraceNotes(Ms::Measure* measure);
static void layoutGraceNotes(Ms::Segment* seg, int staffIdx);
static void repositionGraceNotesAfter(Ms::Segment* segment);
static void appendGraceNotes(Ms::Chord* chord);
};
}

View file

@ -2006,28 +2006,6 @@ void Chord::scanElements(void* data, void (* func)(void*, EngravingItem*), bool
ChordRest::scanElements(data, func, all);
}
void Chord::attachGraceNotes()
{
//Attach graceNotesBefore of this chord to *this* segment
if (!graceNotesBefore().empty()) {
segment()->attachGraceNotesBefore(graceNotesBefore(), track());
LayoutChords::layoutGraceNotes(segment(), staffIdx());
}
//Attach graceNotesAfter of this chord to the *following* segment
if (!graceNotesAfter().empty()) {
Segment* followingSeg = measure()->tick2segment(segment()->tick() + actualTicks(), SegmentType::All);
while (followingSeg && !followingSeg->hasElements(staff2track(staffIdx()), staff2track(staffIdx()) + 3)) {
// If there is nothing on this staff, go to next segment
followingSeg = followingSeg->next();
}
if (followingSeg) {
followingSeg->attachGraceNotesAfter(graceNotesAfter(), track());
LayoutChords::layoutGraceNotes(followingSeg, staffIdx());
}
}
}
//---------------------------------------------------------
// layoutPitched
//---------------------------------------------------------
@ -3307,9 +3285,9 @@ Measure* Chord::measure() const
// graceNotesBefore
//---------------------------------------------------------
std::vector<Chord*> Chord::graceNotesBefore() const
GraceNotesGroup& Chord::graceNotesBefore() const
{
std::vector<Chord*> cl;
_graceNotesBefore.clear();
for (Chord* c : _graceNotes) {
Q_ASSERT(c->noteType() != NoteType::NORMAL && c->noteType() != NoteType::INVALID);
if (c->noteType() & (
@ -3318,27 +3296,27 @@ std::vector<Chord*> Chord::graceNotesBefore() const
| NoteType::GRACE4
| NoteType::GRACE16
| NoteType::GRACE32)) {
cl.push_back(c);
_graceNotesBefore.push_back(c);
}
}
return cl;
return _graceNotesBefore;
}
//---------------------------------------------------------
// graceNotesAfter
//---------------------------------------------------------
std::vector<Chord*> Chord::graceNotesAfter() const
GraceNotesGroup& Chord::graceNotesAfter() const
{
std::vector<Chord*> cl;
_graceNotesAfter.clear();
for (int i = static_cast<int>(_graceNotes.size()) - 1; i >= 0; i--) {
Chord* c = _graceNotes[i];
Q_ASSERT(c->noteType() != NoteType::NORMAL && c->noteType() != NoteType::INVALID);
if (c->noteType() & (NoteType::GRACE8_AFTER | NoteType::GRACE16_AFTER | NoteType::GRACE32_AFTER)) {
cl.push_back(c);
_graceNotesAfter.push_back(c);
}
}
return cl;
return _graceNotesAfter;
}
//---------------------------------------------------------
@ -4119,4 +4097,33 @@ void Chord::setNoteEventLists(std::vector<NoteEventList>& ell)
notes()[i]->setPlayEvents(ell[i]);
}
}
//---------------------------------
// GRACE NOTES
//---------------------------------
GraceNotesGroup::GraceNotesGroup(Chord* c)
: EngravingItem(ElementType::GRACE_NOTES_GROUP, c), _parent(c) {}
void GraceNotesGroup::layout()
{
_shape.clear();
for (int i = this->size() - 1; i >= 0; --i) {
Chord* chord = this->at(i);
Shape chordShape = chord->shape();
double offset;
offset = -std::max(chordShape.minHorizontalDistance(_shape, score()), 0.0);
_shape.add(chordShape.translated(mu::PointF(offset, 0.0)));
double xpos = offset - parent()->rxoffset() - parent()->rxpos();
chord->setPos(xpos, 0.0);
}
}
void GraceNotesGroup::setPos(qreal x, qreal y)
{
for (unsigned i = 0; i < this->size(); ++i) {
Chord* chord = this->at(i);
chord->setPos(chord->pos().x() + x, chord->pos().y() + y);
}
}
}

View file

@ -53,6 +53,22 @@ enum class TremoloChordType : char {
TremoloSingle, TremoloFirstNote, TremoloSecondNote
};
class GraceNotesGroup final : public std::vector<Chord*>, public EngravingItem
{
public:
GraceNotesGroup* clone() const override { return new GraceNotesGroup(*this); }
GraceNotesGroup(Chord* c);
Chord* parent() const { return _parent; }
Shape shape() const { return _shape; }
void layout() override;
void setPos(qreal x, qreal y) override;
private:
Chord* _parent = nullptr;
Shape _shape;
};
//---------------------------------------------------------
// @@ Chord
/// Graphic representation of a chord.
@ -80,7 +96,9 @@ class Chord final : public ChordRest
Arpeggio* _arpeggio = nullptr;
Tremolo* _tremolo = nullptr;
bool _endsGlissando; ///< true if this chord is the ending point of a glissando (needed for layout)
std::vector<Chord*> _graceNotes;
std::vector<Chord*> _graceNotes; // storage for all grace notes
mutable GraceNotesGroup _graceNotesBefore = GraceNotesGroup(this); // will store before-chord grace notes
mutable GraceNotesGroup _graceNotesAfter = GraceNotesGroup(this); // will store after-chord grace notes
size_t _graceIndex; ///< if this is a grace note, index in parent list
DirectionV _stemDirection;
@ -210,12 +228,11 @@ public:
const std::vector<Chord*>& graceNotes() const { return _graceNotes; }
std::vector<Chord*>& graceNotes() { return _graceNotes; }
std::vector<Chord*> graceNotesBefore() const;
std::vector<Chord*> graceNotesAfter() const;
GraceNotesGroup& graceNotesBefore() const;
GraceNotesGroup& graceNotesAfter() const;
size_t graceIndex() const { return _graceIndex; }
void setGraceIndex(size_t val) { _graceIndex = val; }
void attachGraceNotes();
int upLine() const override;
int downLine() const override;

View file

@ -328,8 +328,8 @@ public:
virtual const mu::PointF pos() const { return _pos + _offset; }
virtual qreal x() const { return _pos.x() + _offset.x(); }
virtual qreal y() const { return _pos.y() + _offset.y(); }
void setPos(qreal x, qreal y) { _pos.setX(x), _pos.setY(y); }
void setPos(const mu::PointF& p) { _pos = p; }
virtual void setPos(qreal x, qreal y) { _pos.setX(x), _pos.setY(y); }
virtual void setPos(const mu::PointF& p) { _pos = p; }
mu::PointF& rpos() { return _pos; }
qreal& rxpos() { return _pos.rx(); }
qreal& rypos() { return _pos.ry(); }

View file

@ -76,6 +76,7 @@ class StaffText;
class Ottava;
class Note;
class Chord;
class GraceNotesGroup;
class Rest;
class MMRest;
class LayoutBreak;
@ -401,6 +402,7 @@ public:
CONVERT(BagpipeEmbellishment, BAGPIPE_EMBELLISHMENT)
CONVERT(Lasso, LASSO)
CONVERT(Sticking, STICKING)
CONVERT(GraceNotesGroup, GRACE_NOTES_GROUP)
#undef CONVERT
virtual bool isEngravingItem() const { return false; } // overridden in element.h
@ -716,6 +718,7 @@ CONVERT(Part)
CONVERT(Lasso)
CONVERT(BagpipeEmbellishment)
CONVERT(Sticking)
CONVERT(GraceNotesGroup)
#undef CONVERT
}

View file

@ -222,6 +222,7 @@ static const ElementName elementNames[] = {
{ ElementType::OSSIA, "Ossia", QT_TRANSLATE_NOOP("elementName", "Ossia") },
{ ElementType::BAGPIPE_EMBELLISHMENT,"BagpipeEmbellishment", QT_TRANSLATE_NOOP("elementName", "Bagpipe embellishment") },
{ ElementType::STICKING, "Sticking", QT_TRANSLATE_NOOP("elementName", "Sticking") },
{ ElementType::GRACE_NOTES_GROUP, "GraceNotesGroup", QT_TRANSLATE_NOOP("elementName", "Grace notes group")},
{ ElementType::ROOT_ITEM, "RootItem", QT_TRANSLATE_NOOP("elementName", "Root item") },
{ ElementType::DUMMY, "Dummy", QT_TRANSLATE_NOOP("elementName", "Dummy") },
};
@ -363,6 +364,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent)
case ElementType::SCORE:
case ElementType::BRACKET_ITEM:
case ElementType::OSSIA:
case ElementType::GRACE_NOTES_GROUP:
case ElementType::ROOT_ITEM:
case ElementType::DUMMY:
break;

View file

@ -3220,7 +3220,7 @@ void Measure::layoutMeasureElements()
}
// After the rest of the spacing is calculated we position grace-notes-after.
s.positionGraceNotesAfter();
LayoutChords::repositionGraceNotesAfter(&s);
for (EngravingItem* e : s.elist()) {
if (!e) {
@ -3642,11 +3642,6 @@ qreal Measure::createEndBarLines(bool isLastMeasureInSystem)
}
}
// May have grace notes attached to it so we need to lay them out
for (unsigned stfIdx = 0; stfIdx < score()->staves().size(); ++stfIdx) {
LayoutChords::layoutGraceNotes(seg, stfIdx);
}
// fix segment layout
Segment* s = seg->prevActive();
if (s) {
@ -4572,7 +4567,7 @@ void Measure::stretchMeasureInPracticeMode(qreal targetWidth)
continue;
}
// After the rest of the spacing is calculated we position grace-notes-after.
s.positionGraceNotesAfter();
LayoutChords::repositionGraceNotesAfter(&s);
for (EngravingItem* e : s.elist()) {
if (!e) {
continue;

View file

@ -236,10 +236,9 @@ void Segment::init()
size_t staves = score()->nstaves();
size_t tracks = staves * VOICES;
_elist.assign(tracks, 0);
_preAppendedItems.assign(tracks, 0);
_dotPosX.assign(staves, 0.0);
_shapes.assign(staves, Shape());
_graceNotesBefore.assign(tracks, std::vector<Chord*>());
_graceNotesAfter.assign(tracks, std::vector<Chord*>());
}
//---------------------------------------------------------
@ -493,8 +492,7 @@ void Segment::insertStaff(staff_idx_t staff)
track_idx_t track = staff * VOICES;
for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
_elist.insert(_elist.begin() + track, 0);
_graceNotesBefore.insert(_graceNotesBefore.begin() + track, std::vector<Chord*>());
_graceNotesAfter.insert(_graceNotesAfter.begin() + track, std::vector<Chord*>());
_preAppendedItems.insert(_preAppendedItems.begin() + track, 0);
}
_dotPosX.insert(_dotPosX.begin() + staff, 0.0);
_shapes.insert(_shapes.begin() + staff, Shape());
@ -516,8 +514,7 @@ void Segment::removeStaff(staff_idx_t staff)
{
track_idx_t track = staff * VOICES;
_elist.erase(_elist.begin() + track, _elist.begin() + track + VOICES);
_graceNotesBefore.erase(_graceNotesBefore.begin() + track, _graceNotesBefore.begin() + track + VOICES);
_graceNotesAfter.erase(_graceNotesAfter.begin() + track, _graceNotesAfter.begin() + track + VOICES);
_preAppendedItems.erase(_preAppendedItems.begin() + track, _preAppendedItems.begin() + track + VOICES);
_dotPosX.erase(_dotPosX.begin() + staff);
_shapes.erase(_shapes.begin() + staff);
@ -2239,6 +2236,7 @@ void Segment::createShape(staff_idx_t staffIdx)
s.add(r.translated(bl->pos()), bl);
}
s.addHorizontalSpacing(bl, 0, 0);
addPreAppendedToShape(staffIdx, s);
//s.addHorizontalSpacing(Shape::SPACING_LYRICS, 0, 0);
return;
}
@ -2297,6 +2295,23 @@ void Segment::createShape(staff_idx_t staffIdx)
s.add(e->shape().translated(e->pos()));
}
}
addPreAppendedToShape(staffIdx, s);
}
void Segment::addPreAppendedToShape(int staffIdx, Shape& s)
{
for (unsigned track = staffIdx * VOICES; track < staffIdx * VOICES + VOICES; ++track) {
if (!_preAppendedItems[track]) {
continue;
}
EngravingItem* item = _preAppendedItems[track];
item->layout();
Shape itemShape = item->shape();
double offset = -itemShape.minHorizontalDistance(s, score());
s.add(itemShape.translated(mu::PointF(offset, 0.0)));
item->setPos(offset, 0.0);
}
}
//---------------------------------------------------------
@ -2631,19 +2646,4 @@ Fraction Segment::shortestChordRest() const
}
return shortest;
}
/* positionGraceNotesAfter()
* Moves grace-notes-after to the position of the segment. Needs to be called
* after horizontal spacing is computed, otherwise the position of the segment
* is not known. */
void Segment::positionGraceNotesAfter()
{
int tracks = score()->staves().size() * VOICES;
for (int track = 0; track < tracks; track++) {
for (auto gn : _graceNotesAfter[track]) {
double offset = rxpos() - gn->parentItem()->parentItem()->rxpos();
gn->setPos(gn->pos().x() + offset, 0.0);
}
}
}
} // namespace Ms

View file

@ -73,10 +73,9 @@ class Segment final : public EngravingItem
std::vector<EngravingItem*> _annotations;
std::vector<EngravingItem*> _elist; // EngravingItem storage, size = staves * VOICES.
std::vector<EngravingItem*> _preAppendedItems; // Container for items appended to the left of this segment (example: grace notes), size = staves * VOICES.
std::vector<Shape> _shapes; // size = staves
std::vector<qreal> _dotPosX; // size = staves
std::vector<std::vector<Chord*> > _graceNotesBefore; // We prepare an (initially empty) vector of grace notes for each voice
std::vector<std::vector<Chord*> > _graceNotesAfter; // This will be filled with the grace-notes-after of a *previous* segment.
qreal m_spacing{ 0 };
friend class mu::engraving::Factory;
@ -290,14 +289,13 @@ public:
bool isTimeSigAnnounceType() const { return _segmentType == SegmentType::TimeSigAnnounce; }
bool isMMRestSegment() const;
void attachGraceNotesBefore(std::vector<Chord*> graceNotes, int track) { _graceNotesBefore[track] = graceNotes; }
void attachGraceNotesAfter(std::vector<Chord*> graceNotes, int track) { _graceNotesAfter[track] = graceNotes; }
std::vector<Chord*>& graceNotesBefore(int track) { return _graceNotesBefore[track]; }
std::vector<Chord*>& graceNotesAfter(int track) { return _graceNotesAfter[track]; }
void positionGraceNotesAfter();
Fraction shortestChordRest() const;
EngravingItem* preAppendedItem(int track) { return _preAppendedItems[track]; }
void preAppend(EngravingItem* item, int track) { _preAppendedItems[track] = item; }
void clearPreAppended(int track) { _preAppendedItems[track] = nullptr; }
void addPreAppendedToShape(int staffIdx, Shape& s);
static constexpr SegmentType durationSegmentsMask = SegmentType::ChordRest; // segment types which may have non-zero tick length
};

View file

@ -164,6 +164,7 @@ enum class ElementType {
OSSIA,
BAGPIPE_EMBELLISHMENT,
STICKING,
GRACE_NOTES_GROUP,
ROOT_ITEM,
DUMMY,