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