Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2026 MuseScore Limited
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "edithairpin.h"
24 :
25 : #include "../dom/anchors.h"
26 : #include "../dom/chord.h"
27 : #include "../dom/chordrest.h"
28 : #include "../dom/dynamic.h"
29 : #include "../dom/factory.h"
30 : #include "../dom/hairpin.h"
31 : #include "../dom/measure.h"
32 : #include "../dom/score.h"
33 : #include "../dom/segment.h"
34 : #include "../dom/select.h"
35 :
36 : using namespace mu::engraving;
37 :
38 0 : std::vector<Hairpin*> EditHairpin::addHairpins(Transaction& tx, Score* score, HairpinType type)
39 : {
40 0 : std::vector<Hairpin*> hairpins;
41 :
42 : // add hairpin on each staff if possible
43 0 : const Selection& selection = score->selection();
44 0 : if (selection.isRange() && selection.staffStart() != selection.staffEnd() - 1) {
45 0 : for (staff_idx_t staffIdx = selection.staffStart(); staffIdx < selection.staffEnd(); ++staffIdx) {
46 0 : ChordRest* cr1 = selection.firstChordRest(staffIdx * VOICES);
47 0 : ChordRest* cr2 = selection.lastChordRest(staffIdx * VOICES);
48 0 : Hairpin* h = cr1 ? addHairpin(tx, score, type, cr1, cr2)
49 0 : : addHairpin(tx, score, type, selection.tickStart(), selection.tickEnd(), staff2track(staffIdx));
50 0 : if (h) {
51 0 : hairpins.push_back(h);
52 : }
53 : }
54 : } else {
55 : // for single staff range selection, or single selection,
56 : // find start & end elements elements
57 0 : ChordRest* cr1 = nullptr;
58 0 : ChordRest* cr2 = nullptr;
59 0 : score->getSelectedStartEndChordRests(cr1, cr2);
60 0 : Hairpin* h = addHairpin(tx, score, type, cr1, cr2);
61 0 : if (h) {
62 0 : hairpins.push_back(h);
63 : }
64 : }
65 :
66 0 : for (Hairpin* hairpin : hairpins) {
67 0 : hairpin->setInitialTrackAndVoiceAssignment(hairpin->track(), /*curVoiceOnlyOverride*/ false);
68 : }
69 :
70 0 : return hairpins;
71 0 : }
72 :
73 0 : Hairpin* EditHairpin::addHairpin(Transaction& tx, Score* score, HairpinType type, ChordRest* cr1, ChordRest* cr2)
74 : {
75 0 : if (!cr1) {
76 0 : return nullptr;
77 : }
78 :
79 0 : Hairpin* hairpin = Factory::createHairpin(score->dummy());
80 0 : hairpin->setHairpinType(type);
81 0 : if (type == HairpinType::CRESC_LINE) {
82 0 : hairpin->setBeginText(u"cresc.");
83 0 : hairpin->setContinueText(u"(cresc.)");
84 0 : } else if (type == HairpinType::DIM_LINE) {
85 0 : hairpin->setBeginText(u"dim.");
86 0 : hairpin->setContinueText(u"(dim.)");
87 : }
88 :
89 0 : addHairpin(tx, score, hairpin, cr1, cr2);
90 :
91 0 : return hairpin;
92 : }
93 :
94 0 : Hairpin* EditHairpin::addHairpin(Transaction&, Score* score, HairpinType type, Fraction sTick, Fraction eTick, track_idx_t track)
95 : {
96 0 : Hairpin* hairpin = Factory::createHairpin(score->dummy());
97 0 : hairpin->setHairpinType(type);
98 0 : if (type == HairpinType::CRESC_LINE) {
99 0 : hairpin->setBeginText(u"cresc.");
100 0 : hairpin->setContinueText(u"(cresc.)");
101 0 : } else if (type == HairpinType::DIM_LINE) {
102 0 : hairpin->setBeginText(u"dim.");
103 0 : hairpin->setContinueText(u"(dim.)");
104 : }
105 :
106 0 : hairpin->setTrack(track);
107 0 : hairpin->setTick(sTick);
108 0 : hairpin->setTick2(eTick);
109 :
110 0 : score->undoAddElement(hairpin);
111 :
112 0 : return hairpin;
113 : }
114 :
115 0 : void EditHairpin::addHairpin(Transaction&, Score* score, Hairpin* hairpin, ChordRest* cr1, ChordRest* cr2)
116 : {
117 0 : track_idx_t track = cr1->track();
118 0 : Fraction startTick = cr1->tick();
119 :
120 0 : Fraction endTick = startTick;
121 0 : if (score->selection().isRange()) {
122 0 : endTick = score->selection().tickEnd();
123 : } else {
124 0 : if (cr2 && cr2 != cr1) {
125 0 : endTick = cr2->endTick();
126 : } else {
127 0 : endTick = cr1->isChord() ? toChord(cr1)->endTickIncludingTied() : cr1->endTick();
128 : }
129 0 : const Segment* startSegment = cr2 ? cr2->segment() : cr1->segment();
130 0 : for (const Segment* segment = startSegment; segment && segment->tick() < endTick;
131 0 : segment = segment->next1(SegmentType::Duration)) {
132 0 : if (segment == startSegment) {
133 0 : continue;
134 : }
135 0 : if (segment->findAnnotation(ElementType::DYNAMIC, track, track)) {
136 0 : endTick = segment->tick();
137 0 : break;
138 : }
139 : }
140 : }
141 :
142 0 : hairpin->setTrack(track);
143 0 : hairpin->setTrack2(track);
144 0 : hairpin->setTick(startTick);
145 0 : hairpin->setTick2(endTick);
146 :
147 0 : score->undoAddElement(hairpin);
148 0 : }
149 :
150 0 : void EditHairpin::addHairpinToDynamic(Transaction&, Score* score, Hairpin* hairpin, Dynamic* dynamic)
151 : {
152 0 : track_idx_t track = dynamic->track();
153 0 : hairpin->setTrack(track);
154 0 : hairpin->setTrack2(track);
155 :
156 0 : hairpin->setTick(dynamic->tick());
157 :
158 0 : Segment* dynamicSegment = dynamic->segment();
159 :
160 0 : Chord* startChord = nullptr;
161 0 : for (Segment* segment = dynamicSegment; segment; segment = segment->prev(SegmentType::ChordRest)) {
162 0 : EngravingItem* element = segment->element(track);
163 0 : if (element && element->isChord()) {
164 0 : startChord = toChord(element);
165 0 : break;
166 : }
167 : }
168 :
169 0 : Fraction endTick = startChord ? startChord->endTickIncludingTied() : dynamic->segment()->measure()->endTick();
170 :
171 0 : for (Segment* segment = dynamicSegment; segment && segment->tick() < endTick;
172 0 : segment = segment->next1(SegmentType::Duration)) {
173 0 : if (segment == dynamicSegment) {
174 0 : continue;
175 : }
176 0 : if (segment->findAnnotation(ElementType::DYNAMIC, track, track)) {
177 0 : endTick = segment->tick();
178 0 : break;
179 : }
180 : }
181 :
182 0 : hairpin->setTick2(endTick);
183 :
184 0 : hairpin->setVoiceAssignment(dynamic->voiceAssignment());
185 :
186 0 : score->undoAddElement(hairpin);
187 0 : }
188 :
189 0 : Hairpin* EditHairpin::addHairpinToDynamicOnGripDrag(Transaction&, Score* score, Dynamic* dynamic, bool isLeftGrip,
190 : const muse::PointF& pos)
191 : {
192 0 : const track_idx_t track = dynamic->track();
193 0 : staff_idx_t staffIndex = dynamic->staffIdx();
194 0 : Segment* seg = nullptr;
195 0 : constexpr double spacingFactor = 0.5;
196 :
197 : // Ensure time tick segments are created
198 0 : EditTimeTickAnchors::updateAnchors(dynamic);
199 :
200 : // Find segment of type ChordRest or TimeTick near cursor postion
201 0 : score->dragPosition(pos, &staffIndex, &seg, spacingFactor, /*allowTimeAnchor*/ true);
202 :
203 0 : const bool hasValidTick = seg && (isLeftGrip
204 0 : ? seg->tick() < dynamic->tick()
205 0 : : seg->tick() > dynamic->tick());
206 0 : if (!hasValidTick) {
207 0 : return nullptr;
208 : }
209 :
210 0 : Hairpin* hairpin = Factory::createHairpin(score->dummy());
211 0 : hairpin->setHairpinType(isLeftGrip ? HairpinType::DIM_HAIRPIN : HairpinType::CRESC_HAIRPIN);
212 :
213 0 : hairpin->setTrack(track);
214 0 : hairpin->setTrack2(track);
215 :
216 0 : if (isLeftGrip) {
217 0 : hairpin->setTick(seg->tick());
218 0 : hairpin->setTick2(dynamic->tick());
219 : } else {
220 0 : hairpin->setTick(dynamic->tick());
221 0 : hairpin->setTick2(seg->tick());
222 : }
223 :
224 0 : hairpin->setVoiceAssignment(dynamic->voiceAssignment());
225 :
226 0 : score->undoAddElement(hairpin);
227 :
228 0 : return hairpin;
229 : }
|