Skip to content

Commit 6b8eac6

Browse files
authored
Merge pull request #28118 from mike-spa/hopo
Hammer-on and pull-off implementation
2 parents 0ff1749 + 7b61a1a commit 6b8eac6

File tree

96 files changed

+1513
-81
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+1513
-81
lines changed

src/appshell/qml/Preferences/internal/NoteInput/NoteColorsSection.qml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ BaseSection {
5555
id: warnBendsBox
5656
width: parent.width
5757

58-
text: qsTrc("appshell/preferences", "Color guitar bends outside of playable range")
58+
text: qsTrc("appshell/preferences", "Color guitar bends and hammer-ons/pull-offs outside of playable range")
5959

6060
navigation.name: "WarnBendBox"
6161
navigation.panel: root.navigation

src/engraving/dom/chord.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,33 @@ double Chord::centerX() const
909909
return x;
910910
}
911911

912+
bool Chord::allNotesTiedToNext() const
913+
{
914+
Chord* tiedChord = nullptr;
915+
for (Note* note : m_notes) {
916+
if (!note->tieFor()) {
917+
return false;
918+
}
919+
920+
Note* endNote = note->tieFor()->endNote();
921+
Chord* endChord = endNote ? endNote->chord() : nullptr;
922+
if (!endChord) {
923+
return false;
924+
}
925+
926+
if (!tiedChord) {
927+
tiedChord = endChord;
928+
continue;
929+
}
930+
931+
if (endChord != tiedChord) {
932+
return false;
933+
}
934+
}
935+
936+
return true;
937+
}
938+
912939
//---------------------------------------------------------
913940
// processSiblings
914941
//---------------------------------------------------------

src/engraving/dom/chord.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ class Chord final : public ChordRest
330330

331331
StartEndSlurs& startEndSlurs() { return m_startEndSlurs; }
332332

333+
bool allNotesTiedToNext() const;
334+
333335
private:
334336

335337
friend class Factory;

src/engraving/dom/dom.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ set(DOM_SRC
131131
${CMAKE_CURRENT_LIST_DIR}/harmonicmark.h
132132
${CMAKE_CURRENT_LIST_DIR}/harmony.cpp
133133
${CMAKE_CURRENT_LIST_DIR}/harmony.h
134+
${CMAKE_CURRENT_LIST_DIR}/hammeronpulloff.cpp
135+
${CMAKE_CURRENT_LIST_DIR}/hammeronpulloff.h
134136
${CMAKE_CURRENT_LIST_DIR}/harppedaldiagram.cpp
135137
${CMAKE_CURRENT_LIST_DIR}/harppedaldiagram.h
136138
${CMAKE_CURRENT_LIST_DIR}/hook.cpp

src/engraving/dom/edit.cpp

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "glissando.h"
4545
#include "guitarbend.h"
4646
#include "hairpin.h"
47+
#include "hammeronpulloff.h"
4748
#include "harmony.h"
4849
#include "harppedaldiagram.h"
4950
#include "hook.h"
@@ -667,8 +668,32 @@ Slur* Score::addSlur(ChordRest* firstChordRest, ChordRest* secondChordRest, cons
667668
options.disableOverRepeats = true;
668669
secondChordRest = nextChordRest(firstChordRest, options);
669670

670-
if (!secondChordRest) {
671-
secondChordRest = firstChordRest;
671+
if (!secondChordRest || !secondChordRest->isChord()) {
672+
if (slurTemplate && slurTemplate->isHammerOnPullOff() && firstChordRest->isChord()) {
673+
Note* endNote = GuitarBend::createEndNote(toChord(firstChordRest)->upNote());
674+
if (endNote) {
675+
secondChordRest = endNote->chord();
676+
}
677+
}
678+
if (!secondChordRest) {
679+
secondChordRest = firstChordRest;
680+
}
681+
} else if (secondChordRest->isChord()) {
682+
bool firstChordRestIsTiedToSecond = firstChordRest->isChord() && toChord(firstChordRest)->allNotesTiedToNext()
683+
&& toChord(firstChordRest)->upNote()->tieFor()->endNote()->parent() == secondChordRest;
684+
685+
// Follow chain of tied notes and slur until the last
686+
while (toChord(secondChordRest)->allNotesTiedToNext()) {
687+
secondChordRest = toChord(secondChordRest)->upNote()->tieFor()->endNote()->chord();
688+
}
689+
690+
// If the first chord rest is also tied to this chain, slur to the next non-tied note
691+
if (firstChordRestIsTiedToSecond) {
692+
ChordRest* nextCandidate = nextChordRest(secondChordRest, options);
693+
if (nextCandidate) {
694+
secondChordRest = nextCandidate;
695+
}
696+
}
672697
}
673698
}
674699

@@ -687,7 +712,7 @@ Slur* Score::addSlur(ChordRest* firstChordRest, ChordRest* secondChordRest, cons
687712
slur->setEndElement(secondChordRest);
688713

689714
firstChordRest->score()->undoAddElement(slur);
690-
SlurSegment* ss = new SlurSegment(firstChordRest->score()->dummy()->system());
715+
SlurTieSegment* ss = slur->newSlurTieSegment(firstChordRest->score()->dummy()->system());
691716
ss->setSpannerSegmentType(SpannerSegmentType::SINGLE);
692717
if (firstChordRest == secondChordRest && !(slur->isOutgoing() || slur->isIncoming())) {
693718
ss->setSlurOffset(Grip::END, PointF(3.0 * firstChordRest->style().spatium(), 0.0));
@@ -2507,7 +2532,8 @@ void Score::cmdFlip()
25072532
|| e->isPedalSegment()
25082533
|| e->isLyrics()
25092534
|| e->isBreath()
2510-
|| e->isFermata()) {
2535+
|| e->isFermata()
2536+
|| e->isHammerOnPullOffText()) {
25112537
e->undoChangeProperty(Pid::AUTOPLACE, true);
25122538
// TODO: undoChangeProperty() should probably do this directly
25132539
// see https://musescore.org/en/node/281432
@@ -2603,6 +2629,7 @@ void Score::deleteItem(EngravingItem* el)
26032629
case ElementType::KEYSIG:
26042630
case ElementType::MEASURE_NUMBER:
26052631
case ElementType::SYSTEM_LOCK_INDICATOR:
2632+
case ElementType::HAMMER_ON_PULL_OFF_TEXT:
26062633
break;
26072634
// All other types cannot be removed if generated
26082635
default:
@@ -3046,6 +3073,7 @@ void Score::deleteItem(EngravingItem* el)
30463073
case ElementType::HARMONIC_MARK_SEGMENT:
30473074
case ElementType::PICK_SCRAPE_SEGMENT:
30483075
case ElementType::GUITAR_BEND_SEGMENT:
3076+
case ElementType::HAMMER_ON_PULL_OFF_SEGMENT:
30493077
{
30503078
el = toSpannerSegment(el)->spanner();
30513079
if (el->isTie()) {
@@ -3061,6 +3089,10 @@ void Score::deleteItem(EngravingItem* el)
30613089
}
30623090
break;
30633091

3092+
case ElementType::HAMMER_ON_PULL_OFF_TEXT:
3093+
undoRemoveHopoText(toHammerOnPullOffText(el));
3094+
break;
3095+
30643096
case ElementType::STEM_SLASH: // cannot delete this elements
30653097
case ElementType::HOOK:
30663098
case ElementType::GUITAR_BEND_TEXT:
@@ -5138,7 +5170,7 @@ void Score::cloneVoice(track_idx_t strack, track_idx_t dtrack, Segment* sf, cons
51385170

51395171
if (spanner) {
51405172
// Find and add corresponding slurs and hairpins
5141-
static const std::set<ElementType> SPANNERS_TO_COPY { ElementType::SLUR, ElementType::HAIRPIN };
5173+
static const std::set<ElementType> SPANNERS_TO_COPY { ElementType::SLUR, ElementType::HAMMER_ON_PULL_OFF, ElementType::HAIRPIN };
51425174
auto spanners = score->spannerMap().findOverlapping(start.ticks(), lTick.ticks());
51435175
for (auto i = spanners.begin(); i < spanners.end(); i++) {
51445176
Spanner* sp = i->value;
@@ -5996,6 +6028,7 @@ static void undoChangeNoteVisibility(Note* note, bool visible)
59966028
ElementType::NOTE,
59976029
ElementType::LYRICS,
59986030
ElementType::SLUR,
6031+
ElementType::HAMMER_ON_PULL_OFF,
59996032
ElementType::CHORD, // grace notes
60006033
ElementType::LEDGER_LINE, // temporary objects, impossible to change visibility
60016034
};
@@ -6284,6 +6317,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool
62846317
&& et != ElementType::CHORDLINE
62856318
&& et != ElementType::LYRICS
62866319
&& et != ElementType::SLUR
6320+
&& et != ElementType::HAMMER_ON_PULL_OFF
62876321
&& et != ElementType::TIE
62886322
&& et != ElementType::NOTE
62896323
&& et != ElementType::INSTRUMENT_CHANGE
@@ -6355,6 +6389,7 @@ void Score::undoAddElement(EngravingItem* element, bool addToLinkedStaves, bool
63556389
ElementType::OTTAVA,
63566390
ElementType::TRILL,
63576391
ElementType::SLUR,
6392+
ElementType::HAMMER_ON_PULL_OFF,
63586393
ElementType::VIBRATO,
63596394
ElementType::TEXTLINE,
63606395
ElementType::PEDAL,
@@ -7066,6 +7101,72 @@ void Score::undoRemoveElement(EngravingItem* element, bool removeLinked)
70667101
}
70677102
}
70687103

7104+
void Score::undoRemoveHopoText(HammerOnPullOffText* hopoText)
7105+
{
7106+
Chord* startChord = hopoText->startChord();
7107+
Chord* endChord = hopoText->endChord();
7108+
IF_ASSERT_FAILED(startChord && endChord) {
7109+
return;
7110+
}
7111+
7112+
HammerOnPullOffSegment* hopoSegment = toHammerOnPullOffSegment(hopoText->parentItem());
7113+
HammerOnPullOff* hopo = hopoSegment ? hopoSegment->hammerOnPullOff() : nullptr;
7114+
IF_ASSERT_FAILED(hopo) {
7115+
return;
7116+
}
7117+
7118+
Chord* hopoStartChord = toChord(hopo->startElement());
7119+
Chord* hopoEndChord = toChord(hopo->endElement());
7120+
IF_ASSERT_FAILED(hopoStartChord && hopoEndChord) {
7121+
return;
7122+
}
7123+
7124+
if (startChord == hopoStartChord && endChord == hopoEndChord) {
7125+
undoRemoveElement(hopo);
7126+
return;
7127+
}
7128+
7129+
Fraction hopoStartTick = hopo->tick();
7130+
Fraction hopoEndTick = hopo->tick2();
7131+
Fraction hopoTextStartTick = startChord->tick();
7132+
Fraction hopoTextEndTick = endChord->tick();
7133+
7134+
bool shortenFromStart = (hopoTextStartTick - hopoStartTick) < (hopoEndTick - hopoTextEndTick);
7135+
EditData editData;
7136+
editData.curGrip = shortenFromStart ? Grip::START : Grip::END;
7137+
7138+
if (shortenFromStart) {
7139+
Fraction newStartTick = hopoTextEndTick;
7140+
Fraction newTicks = hopoEndTick - newStartTick;
7141+
hopo->undoChangeProperty(Pid::SPANNER_TICK, newStartTick);
7142+
hopo->undoChangeProperty(Pid::SPANNER_TICKS, newTicks);
7143+
hopo->undoChangeStartEndElements(endChord, hopoEndChord);
7144+
if (startChord != hopoStartChord) {
7145+
HammerOnPullOff* newHopo = Factory::createHammerOnPullOff(score()->dummy());
7146+
newHopo->setTrack(hopo->track());
7147+
newHopo->setTick(hopoStartTick);
7148+
newHopo->setTick2(hopoTextStartTick);
7149+
newHopo->setStartElement(hopoStartChord);
7150+
newHopo->setEndElement(startChord);
7151+
score()->undoAddElement(newHopo);
7152+
}
7153+
} else {
7154+
Fraction newEndTick = hopoTextStartTick;
7155+
Fraction newTicks = newEndTick - hopoStartTick;
7156+
hopo->undoChangeProperty(Pid::SPANNER_TICKS, newTicks);
7157+
hopo->undoChangeStartEndElements(hopoStartChord, startChord);
7158+
if (endChord != hopoEndChord) {
7159+
HammerOnPullOff* newHopo = new HammerOnPullOff(score()->dummy());
7160+
newHopo->setTrack(hopo->track());
7161+
newHopo->setTick(hopoTextEndTick);
7162+
newHopo->setTick2(hopoEndTick);
7163+
newHopo->setStartElement(endChord);
7164+
newHopo->setEndElement(hopoEndChord);
7165+
score()->undoAddElement(newHopo);
7166+
}
7167+
}
7168+
}
7169+
70697170
//---------------------------------------------------------
70707171
// undoChangeSpannerElements
70717172
//---------------------------------------------------------

src/engraving/dom/engravingobject.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,8 @@ bool EngravingObject::isTextBase() const
757757
|| type() == ElementType::MMREST_RANGE
758758
|| type() == ElementType::STICKING
759759
|| type() == ElementType::HARP_DIAGRAM
760-
|| type() == ElementType::GUITAR_BEND_TEXT;
760+
|| type() == ElementType::GUITAR_BEND_TEXT
761+
|| type() == ElementType::HAMMER_ON_PULL_OFF_TEXT;
761762
}
762763

763764
//---------------------------------------------------------

src/engraving/dom/engravingobject.h

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ class GuitarBendText;
8484
class HBox;
8585
class Hairpin;
8686
class HairpinSegment;
87+
class HammerOnPullOff;
88+
class HammerOnPullOffSegment;
89+
class HammerOnPullOffText;
8790
class HarmonicMark;
8891
class HarmonicMarkSegment;
8992
class Harmony;
@@ -346,7 +349,6 @@ class EngravingObject
346349
CONVERT(VBox, VBOX)
347350
CONVERT(TBox, TBOX)
348351
CONVERT(FBox, FBOX)
349-
CONVERT(Slur, SLUR)
350352
CONVERT(Glissando, GLISSANDO)
351353
CONVERT(GlissandoSegment, GLISSANDO_SEGMENT)
352354
CONVERT(GuitarBend, GUITAR_BEND)
@@ -370,7 +372,6 @@ class EngravingObject
370372
CONVERT(Beam, BEAM)
371373
CONVERT(Hook, HOOK)
372374
CONVERT(StemSlash, STEM_SLASH)
373-
CONVERT(SlurSegment, SLUR_SEGMENT)
374375
CONVERT(LaissezVibSegment, LAISSEZ_VIB_SEGMENT)
375376
CONVERT(LaissezVib, LAISSEZ_VIB)
376377
CONVERT(PartialTieSegment, PARTIAL_TIE_SEGMENT)
@@ -457,6 +458,9 @@ class EngravingObject
457458
CONVERT(TimeTickAnchor, TIME_TICK_ANCHOR)
458459
CONVERT(Parenthesis, PARENTHESIS)
459460
CONVERT(ShadowNote, SHADOW_NOTE)
461+
CONVERT(HammerOnPullOff, HAMMER_ON_PULL_OFF)
462+
CONVERT(HammerOnPullOffSegment, HAMMER_ON_PULL_OFF_SEGMENT)
463+
CONVERT(HammerOnPullOffText, HAMMER_ON_PULL_OFF_TEXT)
460464
#undef CONVERT
461465

462466
virtual bool isEngravingItem() const { return false; } // overridden in element.h
@@ -486,6 +490,16 @@ class EngravingObject
486490
|| isNoteLineSegment();
487491
}
488492

493+
bool isSlur() const
494+
{
495+
return type() == ElementType::SLUR || type() == ElementType::HAMMER_ON_PULL_OFF;
496+
}
497+
498+
bool isSlurSegment() const
499+
{
500+
return type() == ElementType::SLUR_SEGMENT || type() == ElementType::HAMMER_ON_PULL_OFF_SEGMENT;
501+
}
502+
489503
bool isLineSegment() const
490504
{
491505
return isGlissandoSegment()
@@ -626,15 +640,17 @@ static inline SlurTieSegment* toSlurTieSegment(EngravingObject* e)
626640
{
627641
assert(
628642
e == 0 || e->type() == ElementType::SLUR_SEGMENT || e->type() == ElementType::TIE_SEGMENT
629-
|| e->type() == ElementType::LAISSEZ_VIB_SEGMENT || e->type() == ElementType::PARTIAL_TIE_SEGMENT);
643+
|| e->type() == ElementType::LAISSEZ_VIB_SEGMENT || e->type() == ElementType::PARTIAL_TIE_SEGMENT
644+
|| e->type() == ElementType::HAMMER_ON_PULL_OFF_SEGMENT);
630645
return (SlurTieSegment*)e;
631646
}
632647

633648
static inline const SlurTieSegment* toSlurTieSegment(const EngravingObject* e)
634649
{
635650
assert(
636651
e == 0 || e->type() == ElementType::SLUR_SEGMENT || e->type() == ElementType::TIE_SEGMENT
637-
|| e->type() == ElementType::LAISSEZ_VIB_SEGMENT || e->type() == ElementType::PARTIAL_TIE_SEGMENT);
652+
|| e->type() == ElementType::LAISSEZ_VIB_SEGMENT || e->type() == ElementType::PARTIAL_TIE_SEGMENT
653+
|| e->type() == ElementType::HAMMER_ON_PULL_OFF_SEGMENT);
638654
return (const SlurTieSegment*)e;
639655
}
640656

@@ -877,5 +893,8 @@ CONVERT(PartialLyricsLine)
877893
CONVERT(PartialLyricsLineSegment)
878894
CONVERT(Parenthesis)
879895
CONVERT(ShadowNote)
896+
CONVERT(HammerOnPullOff)
897+
CONVERT(HammerOnPullOffSegment)
898+
CONVERT(HammerOnPullOffText)
880899
#undef CONVERT
881900
}

src/engraving/dom/factory.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "gradualtempochange.h"
5252
#include "guitarbend.h"
5353
#include "hairpin.h"
54+
#include "hammeronpulloff.h"
5455
#include "harmonicmark.h"
5556
#include "harmony.h"
5657
#include "harppedaldiagram.h"
@@ -220,6 +221,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent)
220221
case ElementType::FIGURED_BASS: return new FiguredBass(parent->isSegment() ? toSegment(parent) : dummy->segment());
221222
case ElementType::STEM: return new Stem(parent->isChord() ? toChord(parent) : dummy->chord());
222223
case ElementType::SLUR: return new Slur(parent);
224+
case ElementType::HAMMER_ON_PULL_OFF: return new HammerOnPullOff(parent);
223225
case ElementType::TIE: return new Tie(parent);
224226
case ElementType::TUPLET: return new Tuplet(parent->isMeasure() ? toMeasure(parent) : dummy->measure());
225227
case ElementType::FINGERING: return new Fingering(parent->isNote() ? toNote(parent) : dummy->note());
@@ -661,6 +663,9 @@ COPY_ITEM_IMPL(Tuplet)
661663
CREATE_ITEM_IMPL(Hairpin, ElementType::HAIRPIN, EngravingItem, isAccessibleEnabled)
662664
MAKE_ITEM_IMPL(Hairpin, EngravingItem)
663665

666+
CREATE_ITEM_IMPL(HammerOnPullOff, ElementType::HAMMER_ON_PULL_OFF, EngravingItem, isAccessibleEnabled)
667+
MAKE_ITEM_IMPL(HammerOnPullOff, EngravingItem)
668+
664669
CREATE_ITEM_IMPL(Glissando, ElementType::GLISSANDO, EngravingItem, isAccessibleEnabled)
665670
MAKE_ITEM_IMPL(Glissando, EngravingItem)
666671

src/engraving/dom/factory.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ class Factory
241241
static Hairpin* createHairpin(EngravingItem* parent, bool isAccessibleEnabled = true);
242242
static std::shared_ptr<Hairpin> makeHairpin(EngravingItem* parent);
243243

244+
static HammerOnPullOff* createHammerOnPullOff(EngravingItem* parent, bool isAccessibleEnabled = true);
245+
static std::shared_ptr<HammerOnPullOff> makeHammerOnPullOff(EngravingItem* parent);
246+
244247
static Glissando* createGlissando(EngravingItem* parent, bool isAccessibleEnabled = true);
245248
static std::shared_ptr<Glissando> makeGlissando(EngravingItem* parent);
246249

0 commit comments

Comments
 (0)