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 "transpose.h"
24 :
25 : #include "iengravingconfiguration.h" // IWYU pragma: keep
26 :
27 : #include "../dom/factory.h"
28 : #include "../dom/fret.h"
29 : #include "../dom/harmony.h"
30 : #include "../dom/keysig.h"
31 : #include "../dom/linkedobjects.h"
32 : #include "../dom/measure.h"
33 : #include "../dom/note.h"
34 : #include "../dom/part.h"
35 : #include "../dom/segment.h"
36 : #include "../dom/staff.h"
37 : #include "../dom/utils.h"
38 : #include "../dom/utils.h"
39 :
40 : #include "editenharmonicspelling.h"
41 : #include "editfretboarddiagram.h"
42 : #include "editkeysig.h"
43 : #include "transaction/transaction.h"
44 :
45 : using namespace mu::engraving;
46 :
47 30 : bool Transpose::transpose(Transaction& tx, Score* score, TransposeMode mode, TransposeDirection direction, Key trKey, int transposeInterval,
48 : bool trKeys, bool transposeChordNames, bool useDoubleSharpsFlats)
49 : {
50 30 : const Selection& selection = score->selection();
51 30 : bool result = true;
52 30 : bool rangeSelection = selection.isRange();
53 30 : staff_idx_t startStaffIdx = 0;
54 30 : staff_idx_t endStaffIdx = 0;
55 30 : Fraction startTick = Fraction(0, 1);
56 30 : if (rangeSelection) {
57 30 : startStaffIdx = selection.staffStart();
58 30 : endStaffIdx = selection.staffEnd();
59 30 : startTick = selection.tickStart();
60 : }
61 :
62 30 : Staff* st = score->staff(startStaffIdx);
63 :
64 30 : Interval interval;
65 30 : if (mode != TransposeMode::DIATONICALLY) {
66 28 : if (mode == TransposeMode::TO_KEY) {
67 : // calculate interval from "transpose to key"
68 : // find the key of the first pitched staff
69 0 : Key key = Key::C;
70 0 : for (staff_idx_t i = startStaffIdx; i < endStaffIdx; ++i) {
71 0 : Staff* s = score->staff(i);
72 0 : if (s->isPitchedStaff(startTick)) {
73 0 : key = s->concertKey(startTick);
74 : // remember this staff to use as basis in transposing key signatures
75 0 : st = s;
76 0 : break;
77 : }
78 : }
79 0 : if (key != trKey) {
80 0 : interval = keydiff2Interval(key, trKey, direction);
81 : } else { //same key, which direction?
82 0 : if (direction == TransposeDirection::UP) {
83 0 : interval = Interval(7, 12);
84 0 : } else if (direction == TransposeDirection::DOWN) {
85 0 : interval = Interval(-7, -12);
86 : } else { //don't do anything for same key and closest direction
87 0 : return true;
88 : }
89 : }
90 : } else {
91 28 : interval = Interval::allIntervals[transposeInterval];
92 28 : if (direction == TransposeDirection::DOWN) {
93 10 : interval.flip();
94 : }
95 : }
96 :
97 28 : if (!rangeSelection) {
98 0 : trKeys = false;
99 : }
100 28 : bool fullOctave = (interval.chromatic % PITCH_DELTA_OCTAVE) == 0;
101 28 : if (fullOctave && (mode != TransposeMode::TO_KEY)) {
102 2 : trKeys = false;
103 2 : transposeChordNames = false;
104 : }
105 : } else { // diatonic transposition
106 2 : if (direction == TransposeDirection::DOWN) {
107 2 : transposeInterval *= -1;
108 : }
109 : }
110 :
111 30 : if (selection.isList()) {
112 0 : const std::list<EngravingItem*>& selectionList = selection.uniqueElements();
113 0 : for (EngravingItem* e : selectionList) {
114 0 : if (!e->staff() || e->staff()->staffType(e->tick())->group() == StaffGroup::PERCUSSION) {
115 0 : continue;
116 : }
117 0 : if (e->staff()->primaryStaff() && e->staff()->primaryStaff()->staffType(Fraction(0, 1))->group() == StaffGroup::PERCUSSION) {
118 0 : continue;
119 : }
120 0 : if (e->isNote()) {
121 0 : Note* note = toNote(e);
122 0 : if (!transposeNote(note, mode, transposeInterval, trKeys, useDoubleSharpsFlats, interval)) {
123 0 : result = false;
124 : }
125 0 : } else if (e->isHarmony() && transposeChordNames) {
126 0 : EngravingObject* parent = e->parent();
127 0 : if (parent && parent->isFretDiagram()) {
128 0 : if (std::find(selectionList.begin(), selectionList.end(), parent) != selectionList.end()) {
129 0 : continue;
130 : }
131 :
132 0 : FretDiagram* fd = toFretDiagram(parent);
133 0 : transposeFretDiagram(tx, toFretDiagram(fd), score, interval, mode, transposeInterval, trKeys, useDoubleSharpsFlats);
134 : } else {
135 0 : transposeHarmony(tx, toHarmony(e), score, interval, mode, transposeInterval, trKeys, useDoubleSharpsFlats);
136 : }
137 0 : } else if (e->isFretDiagram() && transposeChordNames) {
138 0 : transposeFretDiagram(tx, toFretDiagram(e), score, interval, mode, transposeInterval, trKeys, useDoubleSharpsFlats);
139 0 : } else if (e->isKeySig() && mode != TransposeMode::DIATONICALLY && trKeys) {
140 : // TODO: this currently is disabled in dialog
141 : // if we enabled it, then it will need work
142 : // probably the code should look more like the range selection code
143 0 : KeySig* ks = toKeySig(e);
144 0 : if (!ks->isAtonal()) {
145 0 : Key key = st->key(ks->tick());
146 0 : Key cKey = st->concertKey(ks->tick());
147 0 : KeySigEvent ke = ks->keySigEvent();
148 0 : ke.setConcertKey(cKey);
149 0 : ke.setKey(key);
150 0 : tx.push(new ChangeKeySig(ks, ke, ks->showCourtesy()));
151 0 : }
152 : }
153 : }
154 0 : }
155 :
156 : //--------------------------
157 : // process range selection
158 : //--------------------------
159 :
160 30 : std::vector<Staff*> sl;
161 62 : for (staff_idx_t staffIdx = selection.staffStart(); staffIdx < selection.staffEnd(); ++staffIdx) {
162 32 : Staff* s = score->staff(staffIdx);
163 32 : if (s->staffType(Fraction(0, 1))->group() == StaffGroup::PERCUSSION) { // ignore percussion staff
164 0 : continue;
165 : }
166 32 : if (s->primaryStaff() && s->primaryStaff()->staffType(Fraction(0, 1))->group() == StaffGroup::PERCUSSION) {
167 0 : continue;
168 : }
169 32 : if (muse::contains(sl, s)) {
170 0 : continue;
171 : }
172 32 : bool alreadyThere = false;
173 34 : for (Staff* s2 : sl) {
174 2 : if (s2 == s || (s2->links() && s2->links()->contains(s))) {
175 0 : alreadyThere = true;
176 0 : break;
177 : }
178 : }
179 32 : if (!alreadyThere) {
180 32 : sl.push_back(s);
181 : }
182 : }
183 30 : std::vector<track_idx_t> tracks;
184 62 : for (Staff* s : sl) {
185 32 : track_idx_t idx = s->idx() * VOICES;
186 160 : for (voice_idx_t i = 0; i < VOICES; ++i) {
187 128 : if (score->selectionFilter().canSelectVoice(i)) {
188 128 : tracks.push_back(idx + i);
189 : }
190 : }
191 : }
192 :
193 30 : Segment* s1 = selection.startSegment();
194 30 : if (!s1) {
195 0 : return result;
196 : }
197 : // if range start on mmRest, get the actual segment instead
198 30 : if (s1->measure()->isMMRest()) {
199 0 : s1 = score->tick2segment(s1->tick(), true, s1->segmentType(), false);
200 : }
201 : // if range starts with first CR of measure
202 : // then start looping from very beginning of measure
203 : // so we include key signature and can transpose that if requested
204 30 : if (s1->rtick().isZero()) {
205 30 : s1 = s1->measure()->first();
206 : }
207 30 : Segment* s2 = selection.endSegment();
208 270 : for (Segment* segment = s1; segment && segment != s2; segment = segment->next1()) {
209 240 : if (!segment->enabled()) {
210 0 : continue;
211 : }
212 1380 : for (track_idx_t track : tracks) {
213 1140 : if (score->staff(track / VOICES)->staffType(s1->tick())->group() == StaffGroup::PERCUSSION) {
214 0 : continue;
215 : }
216 1140 : EngravingItem* e = segment->element(track);
217 1140 : if (!e) {
218 879 : continue;
219 : }
220 :
221 261 : if (e->isChord()) {
222 110 : Chord* chord = toChord(e);
223 110 : const std::vector<Note*> nl = chord->notes();
224 292 : for (size_t noteIdx = 0; noteIdx < nl.size(); ++noteIdx) {
225 182 : if (!score->selectionFilter().canSelectNoteIdx(noteIdx, nl.size(), selection.rangeContainsMultiNoteChords())) {
226 0 : continue;
227 : }
228 182 : Note* note = nl.at(noteIdx);
229 182 : if (!transposeNote(note, mode, transposeInterval, trKeys, useDoubleSharpsFlats, interval)) {
230 0 : result = false;
231 : }
232 : }
233 110 : for (Chord* g : chord->graceNotes()) {
234 0 : for (Note* n : g->notes()) {
235 0 : if (!transposeNote(n, mode, transposeInterval, trKeys, useDoubleSharpsFlats, interval)) {
236 0 : result = false;
237 : }
238 : }
239 : }
240 261 : } else if (e->isKeySig() && trKeys && mode != TransposeMode::DIATONICALLY) {
241 11 : KeySig* ks = toKeySig(e);
242 11 : Fraction tick = segment->tick();
243 11 : bool startKey = tick == s1->tick() && !isFirstSystemKeySig(ks);
244 11 : bool addKey = ks->isChange();
245 11 : if ((startKey || addKey) && !ks->isAtonal()) {
246 11 : Staff* staff = ks->staff();
247 11 : Key oKey = ks->concertKey();
248 11 : Key nKey = transposeKey(oKey, interval);
249 11 : KeySigEvent ke = ks->keySigEvent();
250 : // don't transpose instrument change KS, was automagically translated when precedent KS was changed
251 11 : if (!ke.forInstrumentChange() || startKey) {
252 11 : ke.setConcertKey(nKey);
253 : }
254 : // undoChangeKey handles linked staves/parts and generating new keysigs as needed
255 : // it always sets the keysig non-generated
256 : // so only call it when needed
257 11 : EditKeySig::undoChangeKeySig(tx, score, staff, tick, ke);
258 11 : }
259 : }
260 : }
261 240 : if (transposeChordNames
262 240 : && (score->selectionFilter().isFiltered(ElementsSelectionFilterTypes::CHORD_SYMBOL)
263 240 : || score->selectionFilter().isFiltered(ElementsSelectionFilterTypes::FRET_DIAGRAM))) {
264 240 : for (EngravingItem* e : segment->annotations()) {
265 46 : if ((!e->isHarmony() && !e->isFretDiagram()) || (!muse::contains(tracks, e->track()))) {
266 0 : continue;
267 : }
268 :
269 46 : if (e->isHarmony()) {
270 14 : transposeHarmony(tx, toHarmony(e), score, interval, mode, transposeInterval, trKeys, useDoubleSharpsFlats);
271 : }
272 :
273 46 : if (e->isFretDiagram()) {
274 32 : transposeFretDiagram(tx, toFretDiagram(e), score, interval, mode, transposeInterval, trKeys, useDoubleSharpsFlats);
275 : }
276 : }
277 : }
278 : }
279 : //
280 : // create missing key signatures
281 : //
282 30 : if (trKeys && (mode != TransposeMode::DIATONICALLY) && (s1->tick() == Fraction(0, 1))) {
283 119 : for (track_idx_t track : tracks) {
284 96 : if (track % VOICES) {
285 72 : continue;
286 : }
287 24 : Segment* seg = score->firstMeasure()->undoGetSegmentR(SegmentType::KeySig, Fraction(0, 1));
288 24 : KeySig* ks = toKeySig(seg->element(track));
289 24 : if (!ks) {
290 13 : ks = Factory::createKeySig(seg);
291 13 : ks->setTrack(track);
292 13 : Key nKey = transposeKey(Key::C, interval);
293 13 : ks->setKey(nKey);
294 13 : score->undoAddElement(ks);
295 : }
296 : }
297 : }
298 30 : return result;
299 30 : }
300 :
301 : //---------------------------------------------------------
302 : // transposeKeys
303 : // key - -7(Cb) - +7(C#)
304 : //---------------------------------------------------------
305 :
306 13 : void Transpose::transposeKeys(Transaction& tx, Score* score, staff_idx_t staffStart, staff_idx_t staffEnd, const Fraction& ts,
307 : const Fraction& tickEnd,
308 : bool flip)
309 : {
310 13 : Fraction tickStart(ts);
311 13 : if (tickStart < Fraction(0, 1)) { // -1 and 0 are valid values to indicate start of score
312 0 : tickStart = Fraction(0, 1);
313 : }
314 26 : for (staff_idx_t staffIdx = staffStart; staffIdx < staffEnd; ++staffIdx) {
315 13 : Staff* st = score->staff(staffIdx);
316 13 : if (st->staffType(tickStart)->group() == StaffGroup::PERCUSSION) {
317 0 : continue;
318 : }
319 :
320 13 : bool createKey = tickStart.isZero();
321 46 : for (Segment* s = score->firstSegment(SegmentType::KeySig); s; s = s->next1(SegmentType::KeySig)) {
322 33 : if (!s->enabled() || s->tick() < tickStart) {
323 2 : continue;
324 : }
325 31 : if (tickEnd != Fraction(-1, 1) && s->tick() >= tickEnd) {
326 0 : break;
327 : }
328 31 : KeySig* ks = toKeySig(s->element(staffIdx * VOICES));
329 31 : if (!ks || ks->generated()) {
330 5 : continue;
331 : }
332 26 : if (!ks->isAtonal()) {
333 22 : Interval v = st->part()->instrument(s->tick())->transpose();
334 22 : KeySigEvent ke = st->keySigEvent(s->tick());
335 22 : PreferSharpFlat pref = ks->part()->preferSharpFlat();
336 22 : Key nKey = ke.concertKey();
337 22 : if (flip && !v.isZero()) {
338 12 : v.flip();
339 12 : nKey = transposeKey(ke.concertKey(), v, pref);
340 : }
341 :
342 22 : ke.setKey(nKey);
343 22 : tx.push(new ChangeKeySig(ks, ke, ks->showCourtesy()));
344 22 : }
345 26 : if (s->tick().isZero()) {
346 10 : createKey = false;
347 : }
348 : }
349 13 : if (createKey && score->firstMeasure()) {
350 0 : Segment* seg = score->firstMeasure()->undoGetSegmentR(SegmentType::KeySig, Fraction(0, 1));
351 0 : seg->setHeader(true);
352 0 : KeySig* ks = Factory::createKeySig(seg);
353 0 : ks->setTrack(staffIdx * VOICES);
354 0 : Interval v = ks->part()->instrument()->transpose();
355 0 : Key cKey = Key::C;
356 0 : Key nKey = cKey;
357 0 : if (flip) {
358 0 : if (!v.isZero()) {
359 0 : v.flip();
360 0 : nKey = transposeKey(Key::C, v, ks->part()->preferSharpFlat());
361 : }
362 : } else {
363 0 : cKey = transposeKey(Key::C, v);
364 0 : nKey = cKey;
365 : }
366 0 : ks->setKey(cKey, nKey);
367 0 : score->undoAddElement(ks);
368 : }
369 : }
370 13 : }
371 :
372 : //---------------------------------------------------------
373 : // transposeSemitone
374 : //---------------------------------------------------------
375 :
376 0 : void Transpose::transposeSemitone(Transaction& tx, Score* score, int step)
377 : {
378 0 : if (step == 0) {
379 0 : return;
380 : }
381 :
382 0 : TransposeDirection dir = step > 0 ? TransposeDirection::UP : TransposeDirection::DOWN;
383 :
384 0 : if (!transpose(tx, score, TransposeMode::BY_INTERVAL, dir, Key::C, 1, true, true, false)) {
385 0 : LOGD("Score::transposeSemitone: failed");
386 : } else {
387 0 : score->setSelectionChanged(true);
388 : }
389 : }
390 :
391 : //---------------------------------------------------------
392 : // transposeDiatonicAlterations
393 : //---------------------------------------------------------
394 0 : void Transpose::transposeDiatonicAlterations(Transaction& tx, Score* score, TransposeDirection direction)
395 : {
396 : // Transpose current selection diatonically (up/down) while keeping degree alterations
397 : // Note: Score::transpose() absolutely requires valid selection before invocation.
398 0 : if (!score->selection().isNone()) {
399 0 : transpose(tx, score, TransposeMode::DIATONICALLY, direction, Key::C, 1, true, true, true);
400 0 : score->setPlayNote(true); // For when selection is a single note, also playback that note
401 0 : score->setSelectionChanged(true); // This will update the on-screen keyboard
402 : }
403 0 : }
404 :
405 : //---------------------------------------------------------
406 : // transpositionChanged
407 : //---------------------------------------------------------
408 5 : void Transpose::transpositionChanged(Transaction& tx, Score* score, Part* part, Interval oldV, Fraction tickStart, Fraction tickEnd)
409 : {
410 5 : if (tickStart == Fraction(-1, 1)) {
411 0 : tickStart = Fraction(0, 1);
412 : }
413 :
414 : // transpose keys first
415 5 : std::set<Score*> scores;
416 10 : for (Staff* ls : part->staff(0)->staffList()) {
417 : // TODO: special handling for linked staves within a score
418 : // could be useful for capo
419 5 : Score* lsScore = ls->score();
420 5 : if (muse::contains(scores, lsScore)) {
421 0 : continue;
422 : }
423 5 : scores.insert(lsScore);
424 5 : Part* lp = ls->part();
425 5 : if (!lsScore->style().styleB(Sid::concertPitch)) {
426 5 : const TrackRange trackRange = lp->trackRange();
427 5 : transposeKeys(tx, lsScore, track2staff(trackRange.startTrack), track2staff(trackRange.endTrack), tickStart, tickEnd, true);
428 : }
429 5 : }
430 :
431 : // now transpose notes and chord symbols
432 39 : for (Segment* s = score->firstSegment(SegmentType::Duration); s; s = s->next1(SegmentType::Duration)) {
433 34 : if (s->tick() < tickStart) {
434 6 : continue;
435 : }
436 28 : if (tickEnd != Fraction(-1, 1) && s->tick() >= tickEnd) {
437 0 : break;
438 : }
439 28 : Interval v = part->staff(0)->transpose(s->tick());
440 28 : v.flip();
441 28 : Interval diffV(oldV.diatonic + v.diatonic, oldV.chromatic + v.chromatic);
442 56 : for (Staff* st : part->staves()) {
443 28 : if (st->staffType(tickStart)->group() == StaffGroup::PERCUSSION) {
444 0 : continue;
445 : }
446 28 : track_idx_t t1 = st->idx() * VOICES;
447 28 : track_idx_t t2 = t1 + VOICES;
448 140 : for (track_idx_t track = t1; track < t2; ++track) {
449 112 : EngravingItem* e = s->element(track);
450 112 : if (e && e->isChord()) {
451 26 : Chord* c = toChord(e);
452 26 : for (Chord* gc : c->graceNotes()) {
453 0 : for (Note* n : gc->notes()) {
454 0 : int tpc = transposeTpc(n->tpc1(), v, true);
455 0 : n->undoChangeProperty(Pid::TPC2, tpc);
456 : }
457 : }
458 52 : for (Note* n : c->notes()) {
459 26 : int tpc = transposeTpc(n->tpc1(), v, true);
460 26 : n->undoChangeProperty(Pid::TPC2, tpc);
461 : }
462 : }
463 : // find chord symbols
464 116 : for (EngravingItem* element : s->annotations()) {
465 4 : if (element->track() != track || !element->isHarmony()) {
466 4 : continue;
467 : }
468 0 : Harmony* h = toHarmony(element);
469 0 : for (EngravingObject* scoreElement : h->linkList()) {
470 0 : if (!scoreElement->style().styleB(Sid::concertPitch)) {
471 0 : undoTransposeHarmony(tx, toHarmony(scoreElement), diffV, false);
472 : }
473 0 : }
474 : }
475 : }
476 : }
477 : }
478 5 : }
479 :
480 0 : void Transpose::transpositionChanged(Transaction& tx, Score* score, Part* part, const Fraction& instrumentTick, Interval oldTransposition)
481 : {
482 0 : Fraction tickStart = instrumentTick;
483 0 : Fraction tickEnd = { -1, 1 };
484 :
485 0 : auto mainInstrumentEndIt = part->instruments().upper_bound(tickStart.ticks());
486 0 : if (mainInstrumentEndIt != part->instruments().cend()) {
487 0 : tickEnd = Fraction::fromTicks(mainInstrumentEndIt->first);
488 : }
489 :
490 0 : transpositionChanged(tx, score, part, oldTransposition, tickStart, tickEnd);
491 0 : }
492 :
493 : //---------------------------------------------------------
494 : // transposeChord
495 : //---------------------------------------------------------
496 :
497 326 : void Transpose::transposeChord(Chord* c, const Fraction& tick)
498 : {
499 326 : Interval dstTranspose = c->staff()->transpose(tick);
500 :
501 326 : if (dstTranspose.isZero()) {
502 765 : for (Note* n : c->notes()) {
503 472 : n->setTpc2(n->tpc1());
504 : }
505 : } else {
506 33 : dstTranspose.flip();
507 162 : for (Note* n : c->notes()) {
508 129 : n->setTpc2(transposeTpc(n->tpc1(), dstTranspose, true));
509 : }
510 : }
511 326 : }
512 :
513 : //---------------------------------------------------------
514 : // keydiff2Interval
515 : // keysig - -7(Cb) - +7(C#)
516 : //---------------------------------------------------------
517 :
518 0 : Interval Transpose::keydiff2Interval(Key oKey, Key nKey, TransposeDirection dir)
519 : {
520 0 : int diatonic = tpc2step(key2Tpc(nKey)) - tpc2step(key2Tpc(oKey));
521 0 : int chromatic = ((int(nKey) - int(oKey)) + (diatonic * TPC_DELTA_ENHARMONIC)) / TPC_DELTA_SEMITONE;
522 :
523 0 : if ((dir == TransposeDirection::CLOSEST && chromatic > 6)
524 0 : || (dir == TransposeDirection::DOWN && chromatic > 0)) {
525 0 : diatonic -= 7;
526 0 : chromatic -= 12;
527 0 : } else if ((dir == TransposeDirection::CLOSEST && chromatic < -6)
528 0 : || (dir == TransposeDirection::UP && chromatic < 0)) {
529 0 : diatonic += 7;
530 0 : chromatic += 12;
531 : }
532 :
533 0 : return Interval(diatonic, chromatic);
534 : }
535 :
536 182 : bool Transpose::transposeNote(Note* note, TransposeMode mode, int transposeInterval, bool trKeys, bool useDoubleSharpsFlats,
537 : Interval interval)
538 : {
539 182 : if (note->deadNote() && note->configuration()->keepDeadNotesUnchangedOnTranspose()) {
540 4 : return true;
541 : }
542 :
543 178 : if (mode == TransposeMode::DIATONICALLY) {
544 34 : return note->transposeDiatonic(transposeInterval, trKeys, useDoubleSharpsFlats);
545 : }
546 :
547 144 : return note->transpose(interval, useDoubleSharpsFlats);
548 : }
549 :
550 30 : void Transpose::transposeHarmony(Transaction& tx, Harmony* harmony, Score* score, Interval interval, TransposeMode mode,
551 : int transposeInterval, bool trKeys,
552 : bool useDoubleSharpsFlats)
553 : {
554 : // TODO also source interval should reflect modified key (f.ex. by prefer flat)
555 : // now we calculate interval from first pitched staff
556 : // but even if it were right staff, this may differ each tick (actual key signature)
557 : // we can add something like "concert pitch rootTpc" to harmony definition, or ...
558 30 : Interval kv = harmony->staff()->transpose(harmony->tick());
559 30 : Interval iv = harmony->part()->instrument(harmony->tick())->transpose();
560 30 : Interval hInterval((interval.diatonic - kv.diatonic + iv.diatonic), (interval.chromatic - kv.chromatic + iv.chromatic));
561 :
562 : // undoTransposeHarmony does not do links
563 : // because it is also used to handle transposing instruments
564 : // and score / parts could be in different concert pitch states
565 62 : for (EngravingObject* se : harmony->linkList()) {
566 32 : Harmony* h = toHarmony(se);
567 32 : if (mode == TransposeMode::DIATONICALLY) {
568 0 : undoTransposeHarmonyDiatonic(tx, h, transposeInterval, useDoubleSharpsFlats, trKeys);
569 : } else {
570 32 : undoTransposeHarmony(tx, h, hInterval, useDoubleSharpsFlats);
571 : }
572 30 : }
573 30 : score->rebuildFretBox();
574 30 : }
575 :
576 32 : void Transpose::transposeFretDiagram(Transaction& tx, FretDiagram* diagram, Score* score, Interval interval, TransposeMode mode,
577 : int transposeInterval, bool trKeys, bool useDoubleSharpsFlats)
578 : {
579 32 : Harmony* harmony = diagram->harmony();
580 :
581 32 : if (harmony) {
582 16 : transposeHarmony(tx, harmony, score, interval, mode, transposeInterval, trKeys, useDoubleSharpsFlats);
583 :
584 16 : std::vector<DiagramInfo> availableDiagrams = diagram->patternsFromHarmony(harmony->plainText());
585 16 : if (availableDiagrams.empty()) {
586 0 : diagram->undoFretClear();
587 0 : MScore::setError(MsError::TRANSPOSE_NO_FRET_DIAGRAM, true);
588 0 : return;
589 : }
590 16 : tx.push(new FretDataChange(diagram, harmony->plainText()));
591 :
592 16 : score->rebuildFretBox();
593 16 : return;
594 16 : }
595 :
596 : // No chord symbol attached. Create harmony from diagram, transpose it, then use the new harmony to update the fret diagram
597 16 : String pattern = diagram->patternFromDiagram();
598 16 : std::vector<String> names = diagram->harmoniesFromPattern(pattern);
599 16 : if (names.empty()) {
600 0 : diagram->undoFretClear();
601 0 : MScore::setError(MsError::TRANSPOSE_NO_FRET_DIAGRAM, true);
602 0 : return;
603 : }
604 :
605 16 : Fraction tick = Fraction(0, 1);
606 16 : Segment* seg = diagram->segment();
607 16 : if (seg) {
608 16 : tick = seg->tick();
609 : }
610 16 : Staff* staff = diagram->staff();
611 16 : Key key = staff ? staff->key(tick) : Key::C;
612 :
613 16 : String name = findBestEnharmonicFit(names, key, score->style());
614 :
615 : // Transpose harmony without undo
616 16 : Interval kv = staff ? staff->transpose(tick) : Interval(0, 0);
617 :
618 16 : Part* part = diagram->part();
619 16 : Instrument* instrument = part ? part->instrument(tick) : nullptr;
620 16 : Interval iv = instrument ? instrument->transpose() : Interval(0, 0);
621 :
622 16 : Interval hInterval((interval.diatonic - kv.diatonic + iv.diatonic), (interval.chromatic - kv.chromatic + iv.chromatic));
623 :
624 32 : String transposed = transposedChordName(score, name, [&](int tpc) {
625 32 : if (mode == TransposeMode::DIATONICALLY) {
626 0 : return transposeTpcDiatonicByKey(tpc, transposeInterval, key, trKeys, useDoubleSharpsFlats);
627 : }
628 32 : return transposeTpc(tpc, hInterval, useDoubleSharpsFlats);
629 16 : });
630 :
631 16 : std::vector<DiagramInfo> availableDiagrams = diagram->patternsFromHarmony(transposed);
632 16 : if (availableDiagrams.empty()) {
633 0 : diagram->undoFretClear();
634 0 : MScore::setError(MsError::TRANSPOSE_NO_FRET_DIAGRAM, true);
635 0 : return;
636 : }
637 16 : tx.push(new FretDataChange(diagram, transposed));
638 :
639 16 : score->rebuildFretBox();
640 16 : }
641 :
642 16 : String Transpose::transposedChordName(Score* score, const String& name, const std::function<int(int tpc)>& transformTpc)
643 : {
644 16 : Harmony h(score->dummy());
645 16 : h.setHarmony(name);
646 32 : for (HarmonyInfo* info : h.chords()) {
647 16 : info->setRootTpc(transformTpc(info->rootTpc()));
648 16 : info->setBassTpc(transformTpc(info->bassTpc()));
649 16 : }
650 16 : h.setXmlText(h.harmonyName());
651 32 : return h.harmonyName();
652 16 : }
653 :
654 16 : String Transpose::findBestEnharmonicFit(const std::vector<String>& notes, Key key, const MStyle& style)
655 : {
656 16 : std::vector<int> tpcs;
657 16 : tpcs.reserve(notes.size());
658 16 : NoteSpellingType harmonySpelling = style.styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
659 16 : NoteCaseType harmonyCase = NoteCaseType::AUTO;
660 16 : size_t idx = 0;
661 38 : for (const String& note : notes) {
662 22 : int tpc = convertNote(note, harmonySpelling, harmonyCase, idx);
663 22 : tpcs.push_back(tpc);
664 : }
665 :
666 16 : int tpc = EditEnharmonicSpelling::bestEnharmonicFit(tpcs, key);
667 :
668 16 : if (tpc == Tpc::TPC_INVALID) {
669 0 : return notes.front();
670 : }
671 :
672 16 : String note = tpc2name(tpc, harmonySpelling, harmonyCase);
673 :
674 16 : return note;
675 16 : }
676 :
677 : //---------------------------------------------------------
678 : // transposeTpcDiatonicByKey
679 : //
680 : // returns the tpc diatonically transposed by steps, using degrees of given key
681 : // option to keep any alteration tpc had with respect to unaltered corresponding degree of key
682 : // option to enharmonically reduce tpc using double alterations
683 : //---------------------------------------------------------
684 :
685 0 : int Transpose::transposeTpcDiatonicByKey(int tpc, int steps, Key key, bool keepAlteredDegrees, bool useDoubleSharpsFlats)
686 : {
687 0 : if (!tpcIsValid(tpc)) {
688 0 : return tpc;
689 : }
690 :
691 : // get step for tpc with alteration for key
692 : int alter;
693 0 : int step = tpc2stepByKey(tpc, key, alter);
694 :
695 : // transpose step and get tpc for step/key
696 0 : step += steps;
697 0 : int newTpc = step2tpcByKey(step, key);
698 :
699 : // if required, apply alteration to new tpc
700 0 : if (keepAlteredDegrees) {
701 0 : newTpc += alter * TPC_DELTA_SEMITONE;
702 : }
703 :
704 : // check results are in ranges
705 0 : return clampEnharmonic(newTpc, useDoubleSharpsFlats);
706 : }
707 :
708 : //---------------------------------------------------------
709 : // transposeKey
710 : //---------------------------------------------------------
711 :
712 4164 : Key Transpose::transposeKey(Key key, const Interval& interval, PreferSharpFlat prefer)
713 : {
714 4164 : int keyAsTpc = key2Tpc(key);
715 4164 : Key newKey = tpc2Key(transposeTpc(keyAsTpc, interval, false));
716 :
717 : // ignore prefer for octave transposing instruments
718 4164 : if (interval.chromatic % PITCH_DELTA_OCTAVE == 0 && interval.diatonic % STEP_DELTA_OCTAVE == 0) {
719 3165 : prefer = PreferSharpFlat::NONE;
720 : }
721 :
722 4164 : return clampKey(newKey, prefer);
723 : }
724 :
725 : //---------------------------------------------------------
726 : // transposeTpc
727 : //
728 : // transposes a pitch spelling by a given interval.
729 : // option to enharmonically reduce tpc using double alterations
730 : //---------------------------------------------------------
731 :
732 15530 : int Transpose::transposeTpc(int tpc, Interval interval, bool useDoubleSharpsFlats)
733 : {
734 15530 : int deltaTpc = (interval.chromatic * TPC_DELTA_SEMITONE) - (interval.diatonic * TPC_DELTA_ENHARMONIC);
735 15530 : if (!tpcIsValid(tpc) || deltaTpc == 0) { // perfect unison & perfect octave
736 13494 : return tpc;
737 : }
738 2036 : return clampEnharmonic(tpc + deltaTpc, useDoubleSharpsFlats);
739 : }
740 :
741 : //---------------------------------------------------------
742 : // undoTransposeHarmony
743 : //---------------------------------------------------------
744 :
745 : namespace {
746 : class TransposeHarmony : public UndoableCommand
747 : {
748 114 : OBJECT_ALLOCATOR(engraving, TransposeHarmony)
749 :
750 : public:
751 57 : TransposeHarmony(Harmony* harmony, Interval interval, bool useDoubleSharpsFlats)
752 114 : : m_harmony(harmony)
753 57 : , m_interval(interval)
754 57 : , m_useDoubleSharpsFlats(useDoubleSharpsFlats)
755 : {
756 57 : }
757 :
758 81 : UNDO_TYPE(CommandType::TransposeHarmony)
759 0 : UNDO_NAME("TransposeHarmony")
760 243 : UNDO_CHANGED_OBJECTS({ m_harmony })
761 :
762 : private:
763 : void flip() override;
764 :
765 : Harmony* m_harmony = nullptr;
766 :
767 : Interval m_interval = Interval(0, 0);
768 : bool m_useDoubleSharpsFlats = false;
769 : };
770 : }
771 :
772 81 : void TransposeHarmony::flip()
773 : {
774 81 : Transpose::doUndoTransposeHarmony(m_harmony, m_interval, m_useDoubleSharpsFlats);
775 81 : m_interval.flip();
776 81 : }
777 :
778 57 : void Transpose::undoTransposeHarmony(Transaction& tx, Harmony* harmony, Interval interval, bool doubleSharpFlat)
779 : {
780 57 : tx.push(new TransposeHarmony(harmony, interval, doubleSharpFlat));
781 57 : }
782 :
783 81 : void Transpose::doUndoTransposeHarmony(Harmony* harmony, Interval interval, bool useDoubleSharpsFlats)
784 : {
785 81 : harmony->realizedHarmony().setDirty(true); // harmony should be re-realized after transposition
786 :
787 162 : for (HarmonyInfo* info : harmony->chords()) {
788 81 : info->setRootTpc(Transpose::transposeTpc(info->rootTpc(), interval, useDoubleSharpsFlats));
789 81 : info->setBassTpc(Transpose::transposeTpc(info->bassTpc(), interval, useDoubleSharpsFlats));
790 81 : }
791 :
792 81 : harmony->setXmlText(harmony->harmonyName());
793 81 : harmony->triggerLayout();
794 81 : }
795 :
796 : //---------------------------------------------------------
797 : // undoTransposeHarmonyDiatonic
798 : //---------------------------------------------------------
799 :
800 : namespace {
801 : class TransposeHarmonyDiatonic : public UndoableCommand
802 : {
803 0 : OBJECT_ALLOCATOR(engraving, TransposeHarmonyDiatonic)
804 :
805 : public:
806 0 : TransposeHarmonyDiatonic(Harmony* harmony, int interval, bool useDoubleSharpsFlats, bool transposeKeys)
807 0 : : m_harmony(harmony)
808 0 : , m_interval(interval)
809 0 : , m_useDoubleSharpsFlats(useDoubleSharpsFlats)
810 0 : , m_transposeKeys(transposeKeys)
811 : {
812 0 : }
813 :
814 0 : UNDO_TYPE(CommandType::TransposeHarmony)
815 0 : UNDO_NAME("TransposeHarmonyDiatonic")
816 0 : UNDO_CHANGED_OBJECTS({ m_harmony })
817 :
818 : private:
819 : void flip() override;
820 :
821 : Harmony* m_harmony = nullptr;
822 : int m_interval = 0;
823 : bool m_useDoubleSharpsFlats = false;
824 : bool m_transposeKeys = false;
825 : };
826 : }
827 :
828 0 : void TransposeHarmonyDiatonic::flip()
829 : {
830 0 : Transpose::doUndoTransposeHarmonyDiatonic(m_harmony, m_interval, m_useDoubleSharpsFlats, m_transposeKeys);
831 :
832 0 : m_interval *= -1;
833 0 : }
834 :
835 0 : void Transpose::undoTransposeHarmonyDiatonic(Transaction& tx, Harmony* harmony, int interval, bool doubleSharpFlat, bool transposeKeys)
836 : {
837 0 : tx.push(new TransposeHarmonyDiatonic(harmony, interval, doubleSharpFlat, transposeKeys));
838 0 : }
839 :
840 0 : void Transpose::doUndoTransposeHarmonyDiatonic(Harmony* harmony, int interval, bool useDoubleSharpsFlats, bool transposeKeys)
841 : {
842 0 : harmony->realizedHarmony().setDirty(true); // harmony should be re-realized after transposition
843 :
844 0 : Fraction tick = Fraction(0, 1);
845 0 : Segment* seg = harmony->getParentSeg();
846 0 : if (seg) {
847 0 : tick = seg->tick();
848 : }
849 0 : Key key = !harmony->staff() ? Key::C : harmony->staff()->key(tick);
850 :
851 0 : for (HarmonyInfo* info : harmony->chords()) {
852 0 : info->setRootTpc(Transpose::transposeTpcDiatonicByKey(info->rootTpc(), interval, key, transposeKeys, useDoubleSharpsFlats));
853 0 : info->setBassTpc(Transpose::transposeTpcDiatonicByKey(info->bassTpc(), interval, key, transposeKeys, useDoubleSharpsFlats));
854 0 : }
855 :
856 0 : harmony->setXmlText(harmony->harmonyName());
857 0 : harmony->triggerLayout();
858 0 : }
|