LCOV - code coverage report
Current view: top level - src/engraving/editing - realizechordsymbols.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 88.8 % 134 119
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) 2026 MuseScore Limited
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program. If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "realizechordsymbols.h"
      24              : 
      25              : #include <vector>
      26              : 
      27              : #include "style/style.h"
      28              : 
      29              : #include "../dom/chord.h"
      30              : #include "../dom/factory.h"
      31              : #include "../dom/harmony.h"
      32              : #include "../dom/instrument.h"
      33              : #include "../dom/measure.h"
      34              : #include "../dom/note.h"
      35              : #include "../dom/part.h"
      36              : #include "../dom/realizedharmony.h"
      37              : #include "../dom/rest.h"
      38              : #include "../dom/score.h"
      39              : #include "../dom/segment.h"
      40              : #include "../dom/select.h"
      41              : #include "../dom/staff.h"
      42              : #include "../dom/tie.h"
      43              : #include "../dom/tuplet.h"
      44              : #include "../dom/utils.h"
      45              : 
      46              : #include "log.h"
      47              : 
      48              : using namespace mu;
      49              : using namespace mu::engraving;
      50              : 
      51              : //---------------------------------------------------------
      52              : //   setChord
      53              : //    'dur' is in local (stretched) time
      54              : //    return segment of last created chord
      55              : //---------------------------------------------------------
      56          492 : static Segment* setChord(Score* score, Segment* segment, track_idx_t track, const Chord* chordTemplate, Fraction dur)
      57              : {
      58          492 :     assert(segment->segmentType() == SegmentType::ChordRest);
      59              : 
      60          492 :     Fraction tick = segment->tick();
      61          492 :     Chord* nr     = nullptr;   //current added chord used so we can select the last added chord and so we can apply ties
      62          492 :     std::vector<Tie*> tie(chordTemplate->notes().size());   //keep pointer to a tie for each note in the chord in case we need to tie notes
      63          492 :     ChordRest* cr = toChordRest(segment->element(track));   //chord rest under the segment for the specified track
      64              : 
      65          492 :     bool addTie = false;
      66              : 
      67          492 :     Measure* measure = nullptr;
      68              :     //keep creating chords and tieing them until we created the full duration asked for (dur)
      69              :     for (;;) {
      70          511 :         if (track % VOICES) {
      71            0 :             score->expandVoice(segment, track);
      72              :         }
      73              : 
      74          511 :         Tuplet* t = cr ? cr->tuplet() : 0;
      75          511 :         Fraction tDur = segment->ticks();
      76          511 :         Segment* seg = segment->next();
      77              : 
      78              :         //we need to get a correct subduration so that makeGap can function properly
      79              :         //since makeGap() takes "normal" duration rather than actual length
      80          536 :         while (seg) {
      81          525 :             if (seg->segmentType() == SegmentType::ChordRest) {
      82              :                 //design choice made to keep multiple notes across a tuplet as tied single notes rather than combining them
      83              :                 //since it's arguably more readable, but the other code is still here (commented)
      84           45 :                 ChordRest* testCr = toChordRest(seg->element(track));
      85              : 
      86              :                 //code here allows us to combine tuplet realization together which I have opted not to do for readability (of the music)
      87              :                 //if (!!t ^ (testCr && testCr->tuplet())) //stop if we started with a tuplet and reach something that's not a tuplet,
      88              :                 //      break;                          //or start with not a tuplet and reach a tuplet
      89              : 
      90           45 :                 if (testCr && testCr->tuplet()) {       //stop on tuplet
      91           10 :                     break;
      92              :                 }
      93           35 :                 tDur += seg->ticks();
      94              :             }
      95          515 :             if (tDur >= dur) {       //do not go further than the duration asked for
      96          490 :                 tDur = dur;
      97          490 :                 break;
      98              :             }
      99           25 :             seg = seg->next();       //iterate only across measure (hence usage of next() rather than next1())
     100              :         }
     101          511 :         if (t) {
     102           11 :             tDur *= t->ratio();       //scale by tuplet ratio to get "normal" length rather than actual length when dealing with tuplets
     103              :         }
     104              :         // the returned gap ends at the measure boundary or at tuplet end
     105          511 :         Fraction dd = score->makeGap(segment, track, tDur, t);
     106              : 
     107          511 :         if (dd.isZero()) {
     108            0 :             LOGD("cannot get gap at %d type: %d/%d", tick.ticks(), dur.numerator(),
     109              :                  dur.denominator());
     110            0 :             break;
     111              :         }
     112              : 
     113          511 :         measure = segment->measure();
     114          511 :         std::vector<TDuration> dl = toDurationList(dd, true);
     115          511 :         size_t n = dl.size();
     116              :         //add chord, tieing when necessary within measure
     117         1023 :         for (size_t i = 0; i < n; ++i) {
     118          512 :             const TDuration& d = dl[i];
     119              : 
     120              :             //create new chord from template and add it
     121          512 :             Chord* chord = Factory::copyChord(*chordTemplate);
     122          512 :             nr = chord;
     123              : 
     124          512 :             chord->setTrack(track);
     125          512 :             chord->setDurationType(d);
     126          512 :             chord->setTicks(d.fraction());
     127          512 :             chord->setTuplet(t);
     128          512 :             score->undoAddCR(chord, measure, tick);
     129              :             //if there is something to tie, complete tie backwards
     130              :             //and add the tie to score
     131          512 :             const std::vector<Note*> notes = chord->notes();
     132          512 :             if (addTie) {
     133            5 :                 for (size_t j = 0; j < notes.size(); ++j) {
     134            4 :                     tie[j]->setEndNote(notes[j]);
     135            4 :                     notes[j]->setTieBack(tie[j]);
     136            4 :                     score->undoAddElement(tie[j]);
     137              :                 }
     138            1 :                 addTie = false;
     139              :             }
     140              :             //if we're not the last element in the duration list,
     141              :             //set tie forward
     142          512 :             if (i + 1 < n) {
     143            5 :                 for (size_t j = 0; j < notes.size(); ++j) {
     144            4 :                     tie[j] = Factory::createTie(notes[j]);
     145            4 :                     tie[j]->setStartNote(notes[j]);
     146            4 :                     tie[j]->setTick(tie[j]->startNote()->tick());
     147            4 :                     tie[j]->setTrack(track);
     148            4 :                     notes[j]->setTieFor(tie[j]);
     149            4 :                     addTie = true;
     150              :                 }
     151              :             }
     152          512 :             score->setPlayChord(true);
     153          512 :             segment = chord->segment();
     154          512 :             tick += chord->actualTicks();
     155          512 :         }
     156              : 
     157              :         //subtract the duration already realized and move on
     158          511 :         if (t) {
     159           11 :             dur -= dd / t->ratio();
     160              :         } else {
     161          500 :             dur -= dd;
     162              :         }
     163              :         //we are done when there is no duration left to realize
     164          511 :         if (dur.isZero()) {
     165          492 :             break;
     166              :         }
     167              : 
     168              :         //go to next segment unless we are at the score (which means we will just be done there)
     169           19 :         Segment* nseg = score->tick2segment(tick, false, SegmentType::ChordRest);
     170           19 :         if (!nseg) {
     171            0 :             LOGD("reached end of score");
     172            0 :             break;
     173              :         }
     174              : 
     175              :         //it is possible that the next measure's ticks have not been computed yet. compute them now
     176           19 :         if (nseg->ticks().isZero()) {
     177            0 :             nseg->measure()->computeTicks();
     178              :         }
     179              : 
     180           19 :         segment = nseg;
     181              : 
     182           19 :         cr = toChordRest(segment->element(track));
     183              : 
     184           19 :         if (!cr) {
     185            0 :             if (track % VOICES) {
     186            0 :                 cr = score->addRest(segment, track, TDuration(DurationType::V_MEASURE), 0);
     187              :             } else {
     188            0 :                 LOGD("no rest in voice 0");
     189            0 :                 break;
     190              :             }
     191              :         }
     192              :         //
     193              :         //  Note does not fit on current measure, create Tie to
     194              :         //  next part of note
     195           19 :         std::vector<Note*> notes = nr->notes();
     196           99 :         for (size_t i = 0; i < notes.size(); ++i) {
     197           80 :             tie[i] = Factory::createTie(notes[i]);
     198           80 :             tie[i]->setStartNote(notes[i]);
     199           80 :             tie[i]->setTick(tie[i]->startNote()->tick());
     200           80 :             tie[i]->setTrack(notes[i]->track());
     201           80 :             notes[i]->setTieFor(tie[i]);
     202              :         }
     203          530 :     }
     204          492 :     if (!tie.empty()) {
     205          492 :         score->connectTies();
     206              :     }
     207          492 :     if (nr) {
     208          492 :         score->select(nr, SelectType::SINGLE, 0);
     209              :     }
     210          492 :     return segment;
     211          492 : }
     212              : 
     213              : //---------------------------------------------------------
     214              : //   realizeChordSymbols
     215              : ///   Realize selected chord symbols into notes on the staff.
     216              : ///
     217              : ///   If a voicing and duration type are specified, the
     218              : ///   harmony voicing settings will be overridden by the
     219              : ///   passed parameters. Otherwise, the settings set on the
     220              : ///   harmony object will be used.
     221              : //---------------------------------------------------------
     222              : 
     223           11 : void RealizeChordSymbols::realizeChordSymbols(Transaction&, Score* score, bool literal, Voicing voicing, HDuration durationType)
     224              : {
     225              :     // Create copy, because setChord selects newly created chord and thus
     226              :     // modifies selection().elements() while we're iterating over it
     227           11 :     const std::vector<EngravingItem*> elist = score->selection().elements();
     228              : 
     229          503 :     for (EngravingItem* e : elist) {
     230          492 :         if (!e->isHarmony()) {
     231            0 :             continue;
     232              :         }
     233          492 :         Harmony* h = toHarmony(e);
     234          492 :         if (!h->isRealizable()) {
     235            0 :             continue;
     236              :         }
     237          492 :         const RealizedHarmony& r = h->getRealizedHarmony();
     238          492 :         Segment* seg = h->ownershipParent()->isSegment() ? toSegment(h->ownershipParent()) : toSegment(
     239          492 :             h->ownershipParent()->ownershipParent());
     240          492 :         Fraction tick = seg->tick();
     241          492 :         Fraction duration = r.getActualDuration(tick.ticks(), durationType) * h->staff()->timeStretch(tick);
     242          492 :         bool concertPitch = score->style().styleB(Sid::concertPitch);
     243              : 
     244          492 :         Chord* chord = Factory::createChord(score->dummy());     //chord template
     245          492 :         chord->setTrack(h->track());     //set track so notes have a track to sit on
     246              : 
     247              :         //create chord from notes
     248          492 :         RealizedHarmony::PitchMap notes;
     249          492 :         if (voicing == Voicing::INVALID || durationType == HDuration::INVALID) {
     250          485 :             notes = r.notes();       //no override, just use notes from realize harmony
     251              :         } else {
     252              :             //generate notes list based on overridden settings
     253            7 :             int offset = 0;
     254            7 :             Interval interval = h->staff()->part()->instrument(h->tick())->transpose();
     255            7 :             if (!concertPitch) {
     256            7 :                 offset = interval.chromatic;
     257              :             }
     258           14 :             notes = r.generateNotes(h->rootTpc(), h->bassTpc(),
     259            7 :                                     literal, voicing, offset);
     260              :         }
     261              : 
     262         2802 :         for (const auto& p : notes) {
     263         2310 :             Note* note = Factory::createNote(chord);
     264         2310 :             NoteVal nval;
     265         2310 :             nval.pitch = p.first;
     266         2310 :             if (concertPitch) {
     267          125 :                 nval.tpc1 = p.second;
     268              :             } else {
     269         2185 :                 nval.tpc2 = p.second;
     270              :             }
     271         2310 :             chord->add(note);       //add note first to set track and such
     272         2310 :             note->setNval(nval, tick);
     273              :         }
     274              : 
     275          492 :         if (!seg->isChordRestType()) {
     276            0 :             Segment* newCrSeg = seg->measure()->undoGetSegment(SegmentType::ChordRest, seg->tick());
     277            0 :             newCrSeg->setTicks(seg->ticks());
     278            0 :             seg = newCrSeg;
     279              :         }
     280              : 
     281          492 :         setChord(score, seg, h->track(), chord, duration);     //add chord using template
     282          492 :         delete chord;
     283          492 :     }
     284           11 : }
        

Generated by: LCOV version 2.0-1