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) 2021 MuseScore Limited and others
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 "editcapo.h"
24 :
25 : #include "dom/measure.h"
26 : #include "dom/note.h"
27 : #include "dom/part.h"
28 : #include "dom/score.h"
29 : #include "dom/staff.h"
30 : #include "dom/stringdata.h"
31 :
32 : namespace mu::engraving {
33 : // static
34 42 : void EditCapo::applyCapoTranspose(int startTick, int endTick, UpdateCtx& ctx)
35 : {
36 42 : staff_idx_t staffIdx = ctx.staff->idx();
37 42 : track_idx_t startTrack = staffIdx * VOICES;
38 42 : track_idx_t endTrack = startTrack + VOICES;
39 :
40 811 : for (MeasureBase* mb = ctx.staff->score()->measures()->first(); mb; mb = mb->next()) {
41 769 : if (!mb->isMeasure()) {
42 36 : continue;
43 : }
44 2554 : for (Segment& seg: toMeasure(mb)->segments()) {
45 1821 : if (!seg.isChordRestType()) {
46 1071 : continue;
47 : }
48 3750 : for (track_idx_t track = startTrack; track < endTrack; ++track) {
49 3000 : EngravingItem* e = seg.element(track);
50 3000 : if (!e || !e->isChord()) {
51 2941 : continue;
52 : }
53 59 : if (e->tick().ticks() < startTick || (-1 != endTick && e->tick().ticks() >= endTick)) {
54 12 : continue;
55 : }
56 :
57 47 : Chord* chord = toChord(e);
58 47 : chord->sortNotes();
59 47 : ctx.stringData = chord->originPart()->stringData(chord->tick(), staffIdx);
60 : // Prefer not change strings for intervals and chords
61 : ctx.possibleFretConflict
62 47 : = chord->notes().size() > 1 || std::any_of(chord->notes().begin(), chord->notes().end(),
63 46 : [&](const Note* n) {
64 46 : return n->fret() < ctx.params.fretPosition;
65 : });
66 :
67 99 : for (auto note: chord->notes()) {
68 52 : if (ctx.updateIgnoredStrings) {
69 0 : updateString(note, ctx);
70 : } else {
71 52 : update(note, ctx);
72 : }
73 52 : if (const GuitarBend* bend = note->bendFor(); bend) {
74 2 : if (GuitarBendType::BEND == bend->bendType()) {
75 2 : Note* startNote = bend->startNote();
76 2 : Note* endNote = bend->endNote();
77 2 : const StringData* stringData = ctx.stringData;
78 2 : const int startString = startNote->string();
79 2 : const int startFret = stringData->fret(startNote->pitch(), startString, startNote->staff(), startNote->tick());
80 2 : if (startFret != startNote->fret()) {
81 2 : startNote->setFret(startFret);
82 : }
83 :
84 2 : if (endNote->fret() != startFret) {
85 2 : endNote->setString(startString);
86 2 : endNote->setFret(startFret);
87 : }
88 2 : GuitarBend::fixNotesFrettingForStandardBend(bend->startNote(), bend->endNote());
89 : }
90 : }
91 : }
92 :
93 47 : for (Chord* g: chord->graceNotes()) {
94 0 : for (Note* n: g->notes()) {
95 0 : if (ctx.updateIgnoredStrings) {
96 0 : updateString(n, ctx);
97 : } else {
98 0 : update(n, ctx);
99 : }
100 : }
101 : }
102 : }
103 : }
104 : }
105 42 : }
106 :
107 : // static
108 46 : void EditCapo::updateNotationForCapoChange(const CapoParams& oldParams, const CapoParams& newParams, const Staff* staff, int startTick,
109 : int endTick)
110 : {
111 46 : UpdateCtx ctx;
112 46 : ctx.staff = staff;
113 46 : ctx.params = newParams;
114 : // These two have to be set in applyCapoTranspose for every chord!
115 : // ctx.stringData = nullptr;
116 : // ctx.possibleFretConflict = false;
117 :
118 46 : Score* score = staff->score();
119 46 : Fraction layoutEndTick = endTick != -1 ? Fraction::fromTicks(endTick) : Fraction::max();
120 46 : score->setLayout(layoutEndTick, staff->idx());
121 :
122 46 : const bool modeChanged = oldParams.transposeMode != newParams.transposeMode;
123 46 : const bool fretChanged = oldParams.fretPosition != newParams.fretPosition;
124 46 : const bool stringsChanged = oldParams.ignoredStrings != newParams.ignoredStrings;
125 46 : const bool activeChanged = oldParams.active != newParams.active;
126 :
127 46 : if (modeChanged) {
128 30 : handleModeChange(oldParams, newParams, startTick, endTick, ctx);
129 30 : return;
130 : }
131 16 : if (fretChanged) {
132 0 : handleFretChange(oldParams, newParams, startTick, endTick, ctx);
133 0 : return;
134 : }
135 16 : if (stringsChanged) {
136 2 : handleStringChanged(newParams, startTick, endTick, ctx);
137 2 : return;
138 : }
139 14 : if (activeChanged) {
140 12 : handleActiveChanged(newParams, startTick, endTick, ctx);
141 12 : return;
142 : }
143 46 : }
144 :
145 : // static
146 52 : void EditCapo::update(Note* note, const UpdateCtx& ctx)
147 : {
148 52 : if (muse::contains(ctx.params.ignoredStrings, (string_idx_t)note->string())) {
149 4 : note->setPitch(ctx.stringData->getPitch(note->string(), note->fret(), ctx.staff));
150 : } else {
151 48 : const int pitch = note->pitch() + ctx.noteOffset;
152 48 : note->setPitch(pitch);
153 : }
154 52 : note->setTpcFromPitch();
155 52 : }
156 :
157 : // static
158 0 : void EditCapo::updateString(Note* note, const UpdateCtx& ctx)
159 : {
160 0 : const int pitch = note->pitch();
161 0 : const int string = note->string();
162 0 : const int fret = note->fret();
163 0 : if (CapoParams::TransposeMode::PLAYBACK_ONLY != ctx.params.transposeMode) {
164 0 : if (muse::contains(ctx.params.ignoredStrings, (string_idx_t)note->string())) {
165 : // Already handled
166 0 : if (pitch == ctx.stringData->getPitch(string, fret, 0)) {
167 0 : return;
168 : }
169 0 : if (CapoParams::TransposeMode::STANDARD_ONLY == ctx.params.transposeMode) {
170 0 : note->setPitch(ctx.stringData->getPitch(note->string(), note->fret(), ctx.staff));
171 : }
172 0 : note->setTpcFromPitch();
173 0 : return;
174 : }
175 0 : if (pitch != ctx.stringData->getPitch(string, fret, -ctx.params.fretPosition)) {
176 0 : if (CapoParams::TransposeMode::STANDARD_ONLY == ctx.params.transposeMode) {
177 0 : note->setPitch(ctx.stringData->getPitch(note->string(), note->fret(), -ctx.params.fretPosition));
178 : }
179 : }
180 : }
181 0 : note->setTpcFromPitch();
182 : }
183 :
184 : // static
185 30 : void EditCapo::handleModeChange(const CapoParams& oldParams, const CapoParams& newParams, int startTick, int endTick, UpdateCtx& ctx)
186 : {
187 30 : switch (oldParams.transposeMode) {
188 20 : case CapoParams::TransposeMode::PLAYBACK_ONLY:
189 20 : if (newParams.transposeMode == CapoParams::TransposeMode::STANDARD_ONLY) {
190 8 : ctx.noteOffset = newParams.fretPosition;
191 : }
192 20 : break;
193 6 : case CapoParams::TransposeMode::STANDARD_ONLY:
194 6 : if (newParams.transposeMode == CapoParams::TransposeMode::PLAYBACK_ONLY) {
195 0 : ctx.noteOffset = -oldParams.fretPosition;
196 6 : } else if (newParams.transposeMode == CapoParams::TransposeMode::TAB_ONLY) {
197 6 : ctx.noteOffset = -oldParams.fretPosition;
198 : }
199 6 : break;
200 4 : case CapoParams::TransposeMode::TAB_ONLY:
201 4 : if (newParams.transposeMode == CapoParams::TransposeMode::STANDARD_ONLY) {
202 0 : ctx.noteOffset = newParams.fretPosition;
203 : }
204 4 : break;
205 : }
206 30 : applyCapoTranspose(startTick, endTick, ctx);
207 30 : }
208 :
209 : // static
210 0 : void EditCapo::handleFretChange(const CapoParams& oldParams, const CapoParams& newParams, int startTick, int endTick, UpdateCtx& ctx)
211 : {
212 0 : if (CapoParams::TransposeMode::STANDARD_ONLY == newParams.transposeMode) {
213 0 : ctx.noteOffset = newParams.fretPosition - oldParams.fretPosition;
214 0 : applyCapoTranspose(startTick, endTick, ctx);
215 : }
216 0 : }
217 :
218 : // static
219 2 : void EditCapo::handleStringChanged(const CapoParams& newParams, int startTick, int endTick, UpdateCtx& ctx)
220 : {
221 2 : ctx.updateIgnoredStrings = true;
222 :
223 2 : switch (newParams.transposeMode) {
224 0 : case CapoParams::TransposeMode::STANDARD_ONLY:
225 0 : ctx.noteOffset = newParams.fretPosition;
226 0 : applyCapoTranspose(startTick, endTick, ctx);
227 0 : break;
228 2 : case CapoParams::TransposeMode::TAB_ONLY:
229 : case CapoParams::TransposeMode::PLAYBACK_ONLY:
230 2 : break;
231 : }
232 2 : }
233 :
234 : // static
235 12 : void EditCapo::handleActiveChanged(const CapoParams& newParams, int startTick, int endTick, UpdateCtx& ctx)
236 : {
237 12 : if (!newParams.active) {
238 : // Treat as whatever mode to playback-only transition
239 10 : switch (newParams.transposeMode) {
240 4 : case CapoParams::TransposeMode::STANDARD_ONLY:
241 4 : ctx.noteOffset = -newParams.fretPosition;
242 4 : break;
243 6 : case CapoParams::TransposeMode::TAB_ONLY:
244 : case CapoParams::TransposeMode::PLAYBACK_ONLY:
245 6 : break;
246 : }
247 : } else {
248 : // Otherwise, as playback-only mode to previous one
249 2 : switch (newParams.transposeMode) {
250 0 : case CapoParams::TransposeMode::STANDARD_ONLY:
251 0 : ctx.noteOffset = newParams.fretPosition;
252 0 : break;
253 2 : case CapoParams::TransposeMode::TAB_ONLY:
254 : case CapoParams::TransposeMode::PLAYBACK_ONLY:
255 2 : break;
256 : }
257 : }
258 12 : applyCapoTranspose(startTick, endTick, ctx);
259 12 : }
260 : } // namespace mu::engraving
|