Skip to content

Commit

Permalink
Merge pull request #18259 from asattely/tremolo-fixes
Browse files Browse the repository at this point in the history
Tremolo Fixes for #18173
  • Loading branch information
RomanPudashkin committed Jun 30, 2023
2 parents 1d166c0 + f4b18c0 commit 18cd25e
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 31 deletions.
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
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.

0 comments on commit 18cd25e

Please sign in to comment.