Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tremolo Fixes for #18173 #18259

Merged
merged 5 commits into from Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 25 additions & 22 deletions src/engraving/layout/v0/beamtremololayout.cpp
Expand Up @@ -546,9 +546,9 @@ bool BeamTremoloLayout::calculateAnchors(const std::vector<ChordRest*>& chordRes

int slant = computeDesiredSlant(startNote, endNote, middleLine, dictator, pointer);
bool isFlat = slant == 0;
int specialSlant = isFlat ? isSlopeConstrained(startNote, endNote) : -1;
bool forceFlat = specialSlant == 0;
bool smallSlant = specialSlant == 1;
SlopeConstraint specialSlant = isFlat ? getSlopeConstraint(startNote, endNote) : SlopeConstraint::NO_CONSTRAINT;
bool forceFlat = specialSlant == SlopeConstraint::FLAT;
bool smallSlant = specialSlant == SlopeConstraint::SMALL_SLOPE;
if (isFlat) {
dictator = m_up ? std::min(pointer, dictator) : std::max(pointer, dictator);
pointer = dictator;
Expand Down Expand Up @@ -874,10 +874,10 @@ int BeamTremoloLayout::computeDesiredSlant(int startNote, int endNote, int middl
if (startNote == endNote) {
return 0;
}
int slopeConstrained = isSlopeConstrained(startNote, endNote);
if (slopeConstrained == 0) {
SlopeConstraint slopeConstrained = getSlopeConstraint(startNote, endNote);
if (slopeConstrained == SlopeConstraint::FLAT) {
return 0;
} else if (slopeConstrained == 1) {
} else if (slopeConstrained == SlopeConstraint::SMALL_SLOPE) {
return dictator > pointer ? -1 : 1;
}

Expand All @@ -889,77 +889,80 @@ int BeamTremoloLayout::computeDesiredSlant(int startNote, int endNote, int middl
return std::min(maxSlope, _maxSlopes[interval]) * (m_up ? 1 : -1);
}

int BeamTremoloLayout::isSlopeConstrained(int startNote, int endNote) const
SlopeConstraint BeamTremoloLayout::getSlopeConstraint(int startNote, int endNote) const
{
// 0 to constrain to flat, 1 to constrain to 0.25, <0 for no constraint
if (startNote == endNote) {
return 0;
return SlopeConstraint::FLAT;
} else if (m_beamType == BeamType::TREMOLO) {
// tremolos don't need the small slope constraint since they only have two notes
return SlopeConstraint::NO_CONSTRAINT;
}
// if a note is more extreme than the endpoints, slope is 0
// p.s. _notes is a sorted vector
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong comment

if (m_notes.size() > 2) {
if (m_elements.size() > 2) {
if (m_up) {
int higherEnd = std::min(startNote, endNote);
if (higherEnd > m_notes[0]) {
return 0; // a note is higher in the staff than the highest end
return SlopeConstraint::FLAT; // a note is higher in the staff than the highest end
}
if (higherEnd == m_notes[0] && higherEnd >= m_notes[1]) {
if (higherEnd > m_notes[1]) {
return 0; // a note is higher in the staff than the highest end
return SlopeConstraint::FLAT; // a note is higher in the staff than the highest end
}
size_t chordCount = m_elements.size();
if (chordCount >= 3 && m_notes.size() >= 3) {
bool middleNoteHigherThanHigherEnd = higherEnd >= m_notes[2];
if (middleNoteHigherThanHigherEnd) {
return 0; // two notes are the same as the highest end (notes [0] [1] and [2] higher than or same as higherEnd)
return SlopeConstraint::FLAT; // two notes are the same as the highest end (notes [0] [1] and [2] higher than or same as higherEnd)
}
bool secondNoteSameHeightAsHigherEnd = startNote < endNote && m_elements[1]->isChord()
&& toChord(m_elements[1])->upLine() == higherEnd;
bool secondToLastNoteSameHeightAsHigherEnd = endNote < startNote && m_elements[chordCount - 2]->isChord() && toChord(
m_elements[chordCount - 2])->upLine() == higherEnd;
if (!(secondNoteSameHeightAsHigherEnd || secondToLastNoteSameHeightAsHigherEnd)) {
return 0; // only one note same as higher end, but it is not a neighbor
return SlopeConstraint::FLAT; // only one note same as higher end, but it is not a neighbor
} else {
// there is a single note next to the highest one with equivalent height
// and they are neighbors. this is our exception, so
// the slope may be a max of 0.25.
return 1;
return SlopeConstraint::SMALL_SLOPE;
}
} else {
return 0; // only two notes in entire beam, in this case startNote == endNote
return SlopeConstraint::FLAT; // only two notes in entire beam, in this case startNote == endNote
}
}
} else {
int lowerEnd = std::max(startNote, endNote);
if (lowerEnd < m_notes[m_notes.size() - 1]) {
return 0;
return SlopeConstraint::FLAT;
}
if (lowerEnd == m_notes[m_notes.size() - 1] && lowerEnd <= m_notes[m_notes.size() - 2]) {
if (lowerEnd < m_notes[m_notes.size() - 2]) {
return 0;
return SlopeConstraint::FLAT;
}
size_t chordCount = m_elements.size();
if (chordCount >= 3 && m_notes.size() >= 3) {
bool middleNoteLowerThanLowerEnd = lowerEnd <= m_notes[m_notes.size() - 3];
if (middleNoteLowerThanLowerEnd) {
return 0;
return SlopeConstraint::FLAT;
}
bool secondNoteSameHeightAsLowerEnd = startNote > endNote && m_elements[1]->isChord()
&& toChord(m_elements[1])->downLine() == lowerEnd;
bool secondToLastNoteSameHeightAsLowerEnd = endNote > startNote && m_elements[chordCount - 2]->isChord() && toChord(
m_elements[chordCount - 2])->downLine() == lowerEnd;
if (!(secondNoteSameHeightAsLowerEnd || secondToLastNoteSameHeightAsLowerEnd)) {
return 0;
return SlopeConstraint::FLAT;
} else {
return 1;
return SlopeConstraint::SMALL_SLOPE;
}
} else {
return 0;
return SlopeConstraint::FLAT;
}
}
}
}
return -1;
return SlopeConstraint::NO_CONSTRAINT;
}

int BeamTremoloLayout::getMaxSlope() const
Expand Down
9 changes: 8 additions & 1 deletion src/engraving/layout/v0/beamtremololayout.h
Expand Up @@ -36,6 +36,13 @@ enum class ActionIconType;
enum class SpannerSegmentType;
}

enum class SlopeConstraint
{
NO_CONSTRAINT,
FLAT,
SMALL_SLOPE,
};

namespace mu::engraving::layout::v0 {
class BeamTremoloLayout
{
Expand Down Expand Up @@ -88,7 +95,7 @@ class BeamTremoloLayout

int getMiddleStaffLine(ChordRest* startChord, ChordRest* endChord, int staffLines) const;
int computeDesiredSlant(int startNote, int endNote, int middleLine, int dictator, int pointer) const;
int isSlopeConstrained(int startNote, int endNote) const;
SlopeConstraint getSlopeConstraint(int startNote, int endNote) const;
void offsetBeamWithAnchorShortening(std::vector<ChordRest*> chordRests, int& dictator, int& pointer, int staffLines,
bool isStartDictator, int stemLengthDictator) const;
bool isValidBeamPosition(int yPos, bool isStart, bool isAscending, bool isFlat, int staffLines, bool isOuter) const;
Expand Down
3 changes: 2 additions & 1 deletion src/engraving/layout/v0/chordlayout.cpp
Expand Up @@ -1278,7 +1278,8 @@ void ChordLayout::computeUp(Chord* item, LayoutContext& ctx)
Chord* c1 = item->_tremolo->chord1();
Chord* c2 = item->_tremolo->chord2();
bool cross = c1->staffMove() != c2->staffMove();
if (cross && item == c1) {
if (item == c1) {
// we have to lay out the tremolo because it hasn't been laid out at all yet, and we need its direction
TLayout::layout(item->_tremolo, ctx);
}
Measure* measure = item->findMeasure();
Expand Down
14 changes: 14 additions & 0 deletions src/engraving/libmscore/noteentry.cpp
Expand Up @@ -722,6 +722,20 @@ Ret Score::insertChordByInsertingTime(const Position& pos)
return make_ret(Ret::Code::UnknownError);
}

// remove all two-note tremolos that end on this tick
for (EngravingItem* e : seg->_elist) {
if (!e || !e->isChord()) {
continue;
}
Chord* c = toChord(e);
Tremolo* t = c->tremolo();
if (t && t->twoNotes() && t->chord2() == c) {
// we have to remove this tremolo because we are adding time in the middle of it
// (if c is chord1 then we're inserting before the trem so it's fine)
undoRemoveElement(t);
}
}

const TDuration duration = _is.duration();
const Fraction fraction = duration.fraction();
const Fraction len = fraction;
Expand Down
24 changes: 17 additions & 7 deletions src/engraving/libmscore/tremolo.cpp
Expand Up @@ -122,7 +122,7 @@ void Tremolo::createBeamSegments()
// inset trem from stems for default style
double slope = (endAnchor.y() - startAnchor.y()) / (endAnchor.x() - startAnchor.x());
double gapSp = stemGapSp;
if (defaultStyle) {
if (defaultStyle || _style == TremoloStyle::TRADITIONAL_ALTERNATE) {
// we can eat into the stemGapSp margin if the anchorpoints are sufficiently close together
double widthSp = (endAnchor.x() - startAnchor.x()) / spatium() - (stemGapSp * 2);
if (!RealIsEqualOrMore(widthSp, 0.6)) {
Expand All @@ -132,14 +132,24 @@ void Tremolo::createBeamSegments()
} else {
gapSp = 0.0;
}
double offset = gapSp * spatium();
startAnchor.rx() += offset;
endAnchor.rx() -= offset;
startAnchor.ry() += offset * slope;
endAnchor.ry() -= offset * slope;
BeamSegment* mainStroke = new BeamSegment(this);
PointF xOffset = PointF(gapSp * spatium(), 0);
PointF yOffset = PointF(0, gapSp * spatium() * slope);
if (_style == TremoloStyle::TRADITIONAL_ALTERNATE) {
mainStroke->line = LineF(startAnchor, endAnchor);
startAnchor += xOffset;
endAnchor -= xOffset;
startAnchor += yOffset;
endAnchor -= yOffset;
} else {
startAnchor += xOffset;
endAnchor -= xOffset;
startAnchor += yOffset;
endAnchor -= yOffset;
mainStroke->line = LineF(startAnchor, endAnchor);
}
mainStroke->level = 0;
mainStroke->line = LineF(startAnchor, endAnchor);

_beamSegments.push_back(mainStroke);
double bboxTop = _up ? std::min(mainStroke->line.y1(), mainStroke->line.y2()) : std::max(mainStroke->line.y1(), mainStroke->line.y2());
double halfWidth = style().styleMM(Sid::beamWidth).val() / 2. * (_up ? -1. : 1.);
Expand Down
Binary file added vtest/scores/trem-beam-styles.mscz
Binary file not shown.
Binary file added vtest/scores/trem-layout-order.mscz
Binary file not shown.
Binary file added vtest/scores/trem-with-extreme-chords.mscz
Binary file not shown.