Skip to content

Hopos & tapping updates #29355

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

Merged
merged 12 commits into from
Aug 21, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<durationFontSize>15</durationFontSize>
<durationFontY>0</durationFontY>
<fretUseTextStyle>1</fretUseTextStyle>
<fretTextStyle>37</fretTextStyle>
<fretTextStyle>tab_fret_number</fretTextStyle>
<linesThrough>0</linesThrough>
<minimStyle>0</minimStyle>
<onLines>1</onLines>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<durationFontSize>15</durationFontSize>
<durationFontY>0</durationFontY>
<fretUseTextStyle>1</fretUseTextStyle>
<fretTextStyle>37</fretTextStyle>
<fretTextStyle>tab_fret_number</fretTextStyle>
<linesThrough>0</linesThrough>
<minimStyle>1</minimStyle>
<onLines>1</onLines>
Expand Down
4 changes: 4 additions & 0 deletions src/app/configs/data/shortcuts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,10 @@
<key>add-slur</key>
<seq>S</seq>
</SC>
<SC>
<key>hammer-on-pull-off</key>
<seq>Alt+H</seq>
</SC>
<SC>
<key>add-dynamic</key>
<seq>Ctrl+D</seq>
Expand Down
4 changes: 4 additions & 0 deletions src/app/configs/data/shortcuts_azerty.xml
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,10 @@
<key>add-slur</key>
<seq>S</seq>
</SC>
<SC>
<key>hammer-on-pull-off</key>
<seq>Alt+H</seq>
</SC>
<SC>
<key>add-dynamic</key>
<seq>Ctrl+D</seq>
Expand Down
4 changes: 4 additions & 0 deletions src/app/configs/data/shortcuts_mac.xml
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,10 @@
<key>add-slur</key>
<seq>S</seq>
</SC>
<SC>
<key>hammer-on-pull-off</key>
<seq>Alt+H</seq>
</SC>
<SC>
<key>add-dynamic</key>
<seq>Ctrl+D</seq>
Expand Down
41 changes: 22 additions & 19 deletions src/engraving/dom/chordrest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1213,26 +1213,29 @@ bool ChordRest::isBefore(const EngravingItem* o) const
return EngravingItem::isBefore(o);
}

int otick = o->tick().ticks();
int t = tick().ticks();
if (t == otick) { // At least one of the chord is a grace, order the grace notes
bool oGraceAfter = otherCr->isGraceAfter();
bool graceAfter = isGraceAfter();
bool oGrace = otherCr->isGrace();
bool grace = isGrace();
// normal note are initialized at graceIndex 0 and graceIndex is 0 based
size_t oGraceIndex = oGrace ? toChord(o)->graceIndex() + 1 : 0;
size_t graceIndex = grace ? toChord(this)->graceIndex() + 1 : 0;
if (oGrace) {
oGraceIndex = toChord(o->explicitParent())->graceNotes().size() - oGraceIndex;
}
if (grace) {
graceIndex = toChord(explicitParent())->graceNotes().size() - graceIndex;
}
otick = otick + (oGraceAfter ? 1 : -1) * static_cast<int>(oGraceIndex);
t = t + (graceAfter ? 1 : -1) * static_cast<int>(graceIndex);
Fraction thisTick = tick();
Fraction otherTick = otherCr->tick();
if (thisTick != otherTick) {
return thisTick < otherTick;
}

bool thisIsGraceBefore = isGraceBefore();
bool otherIsGraceBefore = otherCr->isGraceBefore();
if (thisIsGraceBefore != otherIsGraceBefore) {
return thisIsGraceBefore;
} else if (thisIsGraceBefore) {
return toChord(this)->graceIndex() < toChord(otherCr)->graceIndex();
}
return t < otick;

bool thisIsGraceAfter = isGraceAfter();
bool otherIsGraceAfter = otherCr->isGraceAfter();
if (thisIsGraceAfter != otherIsGraceAfter) {
return !thisIsGraceAfter;
} else if (thisIsGraceAfter) {
return toChord(this)->graceIndex() > toChord(otherCr)->graceIndex();
}

return false;
}

//---------------------------------------------------------
Expand Down
20 changes: 12 additions & 8 deletions src/engraving/dom/hammeronpulloff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ void HammerOnPullOffSegment::setTrack(track_idx_t idx)
}
}

void HammerOnPullOffSegment::setSelected(bool f)
{
for (HammerOnPullOffText* text : m_hopoText) {
text->setSelected(f);
}

SlurTieSegment::setSelected(f);
}

void HammerOnPullOffSegment::updateHopoText()
{
Chord* startChord = nullptr;
Expand Down Expand Up @@ -311,16 +320,11 @@ bool HammerOnPullOffText::isUserModified() const

Color HammerOnPullOffText::curColor() const
{
if (score()->printing()) {
return TextBase::curColor();
}

auto engravingConf = configuration();
if (isValid() || !MScore::warnGuitarBends) {
return selected() || (parentItem() && parentItem()->selected()) ? engravingConf->selectionColor() : TextBase::curColor();
if (!isValid() && MScore::warnGuitarBends && !score()->printing()) {
return selected() ? configuration()->criticalSelectedColor() : configuration()->criticalColor();
}

return selected() || parentItem()->selected() ? engravingConf->criticalSelectedColor() : engravingConf->criticalColor();
return TextBase::curColor();
}

PropertyValue HammerOnPullOffText::propertyDefault(Pid id) const
Expand Down
1 change: 1 addition & 0 deletions src/engraving/dom/hammeronpulloff.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class HammerOnPullOffSegment final : public SlurSegment
EngravingObjectList scanChildren() const override;

void setTrack(track_idx_t idx) override;
void setSelected(bool f) override;

void updateHopoText();
void addHopoText(HammerOnPullOffText* t) { m_hopoText.push_back(t); }
Expand Down
49 changes: 48 additions & 1 deletion src/engraving/rendering/score/alignmentlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
#include "dom/expression.h"
#include "dom/fret.h"
#include "dom/hairpin.h"
#include "dom/hammeronpulloff.h"
#include "dom/harmony.h"
#include "dom/system.h"
#include "dom/tapping.h"
#include "dom/text.h"

namespace mu::engraving::rendering::score {
Expand All @@ -41,14 +43,54 @@ void AlignmentLayout::alignItemsGroup(const std::vector<EngravingItem*>& element
double outermostY = yOpticalCenter(elements.front());
for (const EngravingItem* element : elements) {
double curY = yOpticalCenter(element);
outermostY = element->placeAbove() ? std::min(outermostY, curY) : std::max(outermostY, curY);
outermostY = isAbove(element) ? std::min(outermostY, curY) : std::max(outermostY, curY);
}

for (EngravingItem* element : elements) {
moveItemToY(element, outermostY, system);
}
}

void AlignmentLayout::alignHopoLetters(const HammerOnPullOff* hopo, const System* system)
{
IF_ASSERT_FAILED(!hopo->segmentsEmpty()) {
return;
}

std::vector<EngravingItem*> itemsToAlign;
for (SpannerSegment* seg : hopo->spannerSegments()) {
if (seg->system() == system) {
HammerOnPullOffSegment* hopoSegment = toHammerOnPullOffSegment(seg);
for (HammerOnPullOffText* hopoText : hopoSegment->hopoText()) {
itemsToAlign.push_back(hopoText);
}
break;
}
}

if (itemsToAlign.empty()) {
return;
}

bool above = isAbove(itemsToAlign.front());
for (EngravingItem* item : itemsToAlign) {
if (isAbove(item) != above) {
return; // Don't try to align items split above/below
}
}

ChordRest* scr = toChordRest(hopo->startElement());
if (scr && scr->isChord()) {
for (Articulation* art : toChord(scr)->articulations()) {
if (art->isTapping() && toTapping(art)->text() && isAbove(art) == above) {
itemsToAlign.push_back(art);
}
}
}

alignItemsGroup(itemsToAlign, system);
}

void AlignmentLayout::alignItemsWithTheirSnappingChain(const std::vector<EngravingItem*>& elements, const System* system)
{
std::set<EngravingItem*> alignedItems;
Expand Down Expand Up @@ -232,4 +274,9 @@ double AlignmentLayout::computeAverageY(const std::vector<double>& vecOfY)
double sum = std::accumulate(vecOfY.begin(), vecOfY.end(), 0.0);
return sum / static_cast<double>(vecOfY.size());
}

bool AlignmentLayout::isAbove(const EngravingItem* item)
{
return item->isArticulationFamily() ? toArticulation(item)->ldata()->up() : item->placeAbove();
}
} // namespace mu::engraving::rendering::dev
3 changes: 3 additions & 0 deletions src/engraving/rendering/score/alignmentlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

namespace mu::engraving {
class EngravingItem;
class HammerOnPullOff;
class System;
}

Expand All @@ -40,12 +41,14 @@ class AlignmentLayout
static void alignStaffCenteredItems(const std::vector<EngravingItem*>& elements, const System* system);
static void alignItemsForSystem(const std::vector<EngravingItem*>& elements, const System* system);
static void alignItemsGroup(const std::vector<EngravingItem*>& elements, const System* system);
static void alignHopoLetters(const HammerOnPullOff* hopo, const System* system);

private:
static void moveItemToY(EngravingItem* item, double y, const System* system);
static double yOpticalCenter(const EngravingItem* item);
static void scanConnectedItems(EngravingItem* item, const System* system, std::function<void(EngravingItem*)> func);
static double computeAverageY(const std::vector<double>& vecOfY);
static bool isAbove(const EngravingItem* item);
};
} // namespace mu::engraving::rendering::dev
#endif // MU_ENGRAVING_ALIGNMENTLAYOUT_DEV_H
1 change: 1 addition & 0 deletions src/engraving/rendering/score/chordlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,7 @@ void ChordLayout::layoutArticulations3(Chord* item, Slur* slur, LayoutContext& c
}
Shape aShape = a->shape().translate(a->pos() + item->pos() + s->pos() + m->pos() + item->staffOffset());
Shape sShape = ss->shape().translate(ss->pos());
sShape.removeTypes({ ElementType::HAMMER_ON_PULL_OFF_TEXT });
double minDist = ctx.conf().styleMM(Sid::articulationMinDistance);
double vertClearance = a->up() ? aShape.verticalClearance(sShape) : sShape.verticalClearance(aShape);
if (vertClearance < minDist) {
Expand Down
7 changes: 7 additions & 0 deletions src/engraving/rendering/score/systemlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,13 @@ void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx)
if (ecr && ecr->isChord()) {
ChordLayout::layoutArticulations3(toChord(ecr), slur, ctx);
}
if (slur->isHammerOnPullOff()) {
StaffType* staffType = slur->staff()->staffType(slur->tick());
if ((staffType->isTabStaff() && ctx.conf().styleB(Sid::hopoAlignLettersTabStaves))
|| (!staffType->isTabStaff() && ctx.conf().styleB(Sid::hopoAlignLettersStandardStaves))) {
AlignmentLayout::alignHopoLetters(toHammerOnPullOff(slur), system);
}
}
}

processLines(system, ctx, elementsToLayout.trills);
Expand Down
2 changes: 1 addition & 1 deletion src/engraving/rendering/score/tlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3313,7 +3313,7 @@ void TLayout::layoutHammerOnPullOffSegment(HammerOnPullOffSegment* item, LayoutC
bool above = hopoText->placeAbove();

Align align;
align.vertical = above ? AlignV::BASELINE : AlignV::TOP;
align.vertical = AlignV::BASELINE;
align.horizontal = AlignH::HCENTER;
hopoText->setAlign(align);
layoutItem(hopoText, ctx);
Expand Down
2 changes: 2 additions & 0 deletions src/engraving/style/styledef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,8 @@ const std::array<StyleDef::StyleValue, size_t(Sid::STYLES)> StyleDef::styleValue
styleDef(hopoShowOnTabStaves, true),
styleDef(hopoUpperCase, true),
styleDef(hopoShowAll, true),
styleDef(hopoAlignLettersStandardStaves, true),
styleDef(hopoAlignLettersTabStaves, true),

styleDef(lhTappingSymbolNormalStave, LHTappingSymbol::DOT),
styleDef(lhTappingSymbolTab, LHTappingSymbol::DOT),
Expand Down
2 changes: 2 additions & 0 deletions src/engraving/style/styledef.h
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,8 @@ enum class Sid {
hopoShowOnTabStaves,
hopoUpperCase,
hopoShowAll,
hopoAlignLettersStandardStaves,
hopoAlignLettersTabStaves,

lhTappingSymbolNormalStave,
lhTappingSymbolTab,
Expand Down
2 changes: 1 addition & 1 deletion src/notation/internal/notationinteraction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2441,7 +2441,7 @@ void NotationInteraction::applyPaletteElementToList(EngravingItem* element, bool
return;
}

if (element->isSlur() && addSingle) {
if (element->isSlur()) {
doAddSlur(toSlur(element));
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,33 @@ StyledFlickable {
}
}

StyledGroupBox {
Layout.fillWidth: true
Layout.minimumWidth: 500
title: qsTrc("notation/editstyle/hammeronpulloff", "Alignment")

ColumnLayout {
width: parent.width
spacing: 10

StyledTextLabel {
text: qsTrc("notation/editstyle/hammeronpulloff", "Align ‘H’, ‘P’ and ‘T’ symbols in the same slur on")
}

CheckBox {
text: qsTrc("notation/editstyle/hammeronpulloff", "Standard staves")
checked: hopoPage.hopoAlignLettersStandardStaves.value === true
onClicked: hopoPage.hopoAlignLettersStandardStaves.value = !hopoPage.hopoAlignLettersStandardStaves.value
}

CheckBox {
text: qsTrc("notation/editstyle/hammeronpulloff", "Tablature staves")
checked: hopoPage.hopoAlignLettersTabStaves.value === true
onClicked: hopoPage.hopoAlignLettersTabStaves.value = !hopoPage.hopoAlignLettersTabStaves.value
}
}
}

StyledGroupBox {
Layout.fillWidth: true
Layout.minimumWidth: 500
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ HammerOnPullOffTappingPageModel::HammerOnPullOffTappingPageModel(QObject* parent
StyleId::lhTappingShowItemsTab,
StyleId::lhTappingSlurTopAndBottomNoteOnTab,
StyleId::rhTappingSymbolNormalStave,
StyleId::rhTappingSymbolTab, })
StyleId::rhTappingSymbolTab,
StyleId::hopoAlignLettersStandardStaves,
StyleId::hopoAlignLettersTabStaves, })
{
}

Expand Down Expand Up @@ -93,3 +95,13 @@ StyleItem* HammerOnPullOffTappingPageModel::rhTappingSymbolTab() const
{
return styleItem(StyleId::rhTappingSymbolTab);
}

StyleItem* HammerOnPullOffTappingPageModel::hopoAlignLettersStandardStaves() const
{
return styleItem(StyleId::hopoAlignLettersStandardStaves);
}

StyleItem* HammerOnPullOffTappingPageModel::hopoAlignLettersTabStaves() const
{
return styleItem(StyleId::hopoAlignLettersTabStaves);
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class HammerOnPullOffTappingPageModel : public AbstractStyleDialogModel
Q_PROPERTY(StyleItem * rhTappingSymbolNormalStave READ rhTappingSymbolNormalStave CONSTANT)
Q_PROPERTY(StyleItem * rhTappingSymbolTab READ rhTappingSymbolTab CONSTANT)

Q_PROPERTY(StyleItem * hopoAlignLettersStandardStaves READ hopoAlignLettersStandardStaves CONSTANT)
Q_PROPERTY(StyleItem * hopoAlignLettersTabStaves READ hopoAlignLettersTabStaves CONSTANT)

public:
explicit HammerOnPullOffTappingPageModel(QObject* parent = nullptr);

Expand All @@ -59,5 +62,8 @@ class HammerOnPullOffTappingPageModel : public AbstractStyleDialogModel

StyleItem* rhTappingSymbolNormalStave() const;
StyleItem* rhTappingSymbolTab() const;

StyleItem* hopoAlignLettersStandardStaves() const;
StyleItem* hopoAlignLettersTabStaves() const;
};
}
Binary file added vtest/scores/hopo-4.mscz
Binary file not shown.
Loading