diff --git a/src/app/configs/data/shortcuts.xml b/src/app/configs/data/shortcuts.xml
index 962f9345c0352..c9c01b6700bdb 100644
--- a/src/app/configs/data/shortcuts.xml
+++ b/src/app/configs/data/shortcuts.xml
@@ -600,6 +600,14 @@
add-hairpin-reverse
Shift+.
+
+ increase-dynamic
+ Alt+.
+
+
+ decrease-dynamic
+ Alt+,
+
time-delete
Ctrl+Del
diff --git a/src/app/configs/data/shortcuts_mac.xml b/src/app/configs/data/shortcuts_mac.xml
index b75fcf42d3264..0c8ecce0ae8ca 100644
--- a/src/app/configs/data/shortcuts_mac.xml
+++ b/src/app/configs/data/shortcuts_mac.xml
@@ -603,6 +603,14 @@
add-hairpin-reverse
Shift+.
+
+
+ increase-dynamic
+ Alt+.
+
+
+ decrease-dynamic
+ Alt+,
time-delete
diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h
index 1f86f5c71ee90..3c478b2d5db82 100644
--- a/src/notation/inotationinteraction.h
+++ b/src/notation/inotationinteraction.h
@@ -224,6 +224,9 @@ class INotationInteraction
virtual void autoFlipHairpinsType(engraving::Dynamic* selDyn) = 0;
virtual void toggleDynamicPopup() = 0;
+ virtual void increaseDecreaseDynamic(engraving::Dynamic* dynamic, int delta) = 0;
+ virtual void increaseDecreaseDynamicsForSelection(int delta) = 0;
+
virtual bool toggleLayoutBreakAvailable() const = 0;
virtual void toggleLayoutBreak(LayoutBreakType breakType) = 0;
virtual void moveMeasureToPrevSystem() = 0;
diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp
index 1e8eadf0b0069..767a9a40eeddd 100644
--- a/src/notation/internal/notationinteraction.cpp
+++ b/src/notation/internal/notationinteraction.cpp
@@ -5764,6 +5764,82 @@ void NotationInteraction::addHairpinsToSelection(HairpinType type)
}
}
+void NotationInteraction::increaseDecreaseDynamic(Dynamic* dynamic, int delta)
+{
+ const DynamicType currentType = dynamic->dynamicType();
+ auto inRange = [](DynamicType type) {
+ return type >= DynamicType::PPPPP && type <= DynamicType::FFFFF;
+ };
+ if (!inRange(currentType)) {
+ return;
+ }
+ DynamicType newType = static_cast(static_cast(currentType) + delta);
+ if (!inRange(newType)) {
+ return;
+ }
+
+ dynamic->undoChangeProperty(Pid::DYNAMIC_TYPE, newType);
+ dynamic->undoChangeProperty(Pid::TEXT, Dynamic::dynamicText(newType));
+ autoFlipHairpinsTypeImpl(dynamic);
+}
+
+std::vector findDynamicsForElement(const EngravingItem* element)
+{
+ const ChordRest* cr = nullptr;
+
+ if (element->isChordRest()) {
+ cr = toChordRest(element);
+ } else if (element->isNote()) {
+ auto parent = element->parent();
+ if (parent && parent->isChordRest()) {
+ cr = toChordRest(parent);
+ }
+ }
+
+ if (!cr) {
+ return {};
+ }
+
+ Segment* segment = cr->segment();
+
+ std::vector dynamics;
+ for (EngravingItem* item : segment->annotations()) {
+ if (!item->isDynamic()) {
+ continue;
+ }
+
+ Dynamic* dynamic = toDynamic(item);
+ if (dynamic->elementAppliesToTrack(cr->track())
+ && (dynamic->staffIdx() == cr->staffIdx() || dynamic->appliesToAllVoicesInInstrument())) {
+ dynamics.push_back(dynamic);
+ }
+ }
+
+ return dynamics;
+}
+
+void NotationInteraction::increaseDecreaseDynamicsForSelection(int delta)
+{
+ if (selection()->isNone()) {
+ return;
+ }
+ startEdit(TranslatableString("undoableAction", delta > 0 ? "Increase dynamics" : "Decrease dynamics"));
+ if (selection()->element() && !selection()->element()->isDynamic()) {
+ std::vector dynamics = findDynamicsForElement(selection()->element());
+ for (Dynamic* dynamic : dynamics) {
+ increaseDecreaseDynamic(dynamic, delta);
+ }
+ } else {
+ for (EngravingItem* item : selection()->elements()) {
+ if (!item->isDynamic()) {
+ continue;
+ }
+ increaseDecreaseDynamic(toDynamic(item), delta);
+ }
+ }
+ apply();
+}
+
void NotationInteraction::putRestToSelection()
{
mu::engraving::InputState& is = score()->inputState();
@@ -6026,20 +6102,10 @@ void NotationInteraction::increaseDecreaseDuration(int steps, bool stepByDots)
notifyAboutNotationChanged();
}
-void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn)
+void NotationInteraction::autoFlipHairpinsTypeImpl(Dynamic* selDyn)
{
- if (!selDyn) {
- return;
- }
-
- if (selDyn->dynamicType() == DynamicType::OTHER || selDyn->dynamicType() >= DynamicType::FP) {
- return;
- }
-
selDyn->findAdjacentHairpins();
- startEdit(TranslatableString("undoableAction", "Change hairpin type"));
-
if (Hairpin* leftHp = selDyn->leftHairpin()) {
const Dynamic* startDyn = leftHp->dynamicSnappedBefore();
if (startDyn
@@ -6065,6 +6131,20 @@ void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn)
}
}
}
+}
+
+void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn)
+{
+ if (!selDyn) {
+ return;
+ }
+
+ if (selDyn->dynamicType() == DynamicType::OTHER || selDyn->dynamicType() >= DynamicType::FP) {
+ return;
+ }
+
+ startEdit(TranslatableString("undoableAction", "Change hairpin type"));
+ autoFlipHairpinsTypeImpl(selDyn);
apply();
}
diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h
index 796b6dcb42937..6c258948cca4c 100644
--- a/src/notation/internal/notationinteraction.h
+++ b/src/notation/internal/notationinteraction.h
@@ -239,8 +239,12 @@ class NotationInteraction : public INotationInteraction, public muse::Contextabl
void increaseDecreaseDuration(int steps, bool stepByDots) override;
void autoFlipHairpinsType(engraving::Dynamic* selDyn) override;
+ void autoFlipHairpinsTypeImpl(engraving::Dynamic* selDyn);
void toggleDynamicPopup() override;
+ void increaseDecreaseDynamic(engraving::Dynamic* dynamic, int delta) override;
+ void increaseDecreaseDynamicsForSelection(int delta) override;
+
bool toggleLayoutBreakAvailable() const override;
void toggleLayoutBreak(LayoutBreakType breakType) override;
void moveMeasureToPrevSystem() override;
diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h
index 8269962de6366..09ac25cfe2a61 100644
--- a/src/notation/tests/mocks/notationinteractionmock.h
+++ b/src/notation/tests/mocks/notationinteractionmock.h
@@ -185,6 +185,7 @@ class NotationInteractionMock : public INotationInteraction
MOCK_METHOD(void, autoFlipHairpinsType, (engraving::Dynamic * selDyn), (override));
MOCK_METHOD(void, toggleDynamicPopup, (), (override));
+ MOCK_METHOD(void, increaseDecreaseDynamicsForSelection, (int), (override));
MOCK_METHOD(bool, toggleLayoutBreakAvailable, (), (const, override));
MOCK_METHOD(void, toggleLayoutBreak, (LayoutBreakType), (override));
MOCK_METHOD(void, moveMeasureToPrevSystem, (), (override));
diff --git a/src/notationscene/internal/notationactioncontroller.cpp b/src/notationscene/internal/notationactioncontroller.cpp
index 19b59d361cb10..0caf036271990 100644
--- a/src/notationscene/internal/notationactioncontroller.cpp
+++ b/src/notationscene/internal/notationactioncontroller.cpp
@@ -341,6 +341,16 @@ void NotationActionController::init()
registerAction("add-hairpin", &Interaction::addHairpinsToSelection, HairpinType::CRESC_HAIRPIN, &Controller::noteOrRestSelected);
registerAction("add-hairpin-reverse", &Interaction::addHairpinsToSelection, HairpinType::DIM_HAIRPIN,
&Controller::noteOrRestSelected);
+
+ registerAction("increase-dynamic", [this]() {
+ currentNotationInteraction()->increaseDecreaseDynamicsForSelection(
+ 1);
+ }, &Controller::hasSelection);
+ registerAction("decrease-dynamic", [this]() {
+ currentNotationInteraction()->increaseDecreaseDynamicsForSelection(
+ -1);
+ }, &Controller::hasSelection);
+
registerAction("add-noteline", &Interaction::addAnchoredLineToSelectedNotes);
registerAction("add-image", [this]() { addImage(); });
diff --git a/src/notationscene/internal/notationuiactions.cpp b/src/notationscene/internal/notationuiactions.cpp
index c808570d547d2..728694c40b7ae 100644
--- a/src/notationscene/internal/notationuiactions.cpp
+++ b/src/notationscene/internal/notationuiactions.cpp
@@ -1263,6 +1263,18 @@ const UiActionList NotationUiActions::s_actions = {
TranslatableString("action", "&Diminuendo"),
TranslatableString("action", "Add hairpin: diminuendo")
),
+ UiAction("increase-dynamic",
+ mu::context::UiCtxProjectOpened,
+ mu::context::CTX_NOTATION_OPENED,
+ TranslatableString("action", "Increase dynamics"),
+ TranslatableString("action", "Increase selected dynamics")
+ ),
+ UiAction("decrease-dynamic",
+ mu::context::UiCtxProjectOpened,
+ mu::context::CTX_NOTATION_OPENED,
+ TranslatableString("action", "Decrease dynamics"),
+ TranslatableString("action", "Decrease selected dynamics")
+ ),
UiAction("add-noteline",
mu::context::UiCtxProjectOpened,
mu::context::CTX_NOTATION_OPENED,