LCOV - code coverage report
Current view: top level - src/engraving/editing - transpose.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 61.6 % 461 284
Test Date: 2026-11-05 04:07:02

            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 : }
        

Generated by: LCOV version 2.0-1