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 "playbackcontext.h"
24 :
25 : #include <cmath>
26 :
27 : #include "dom/lyrics.h"
28 : #include "dom/measure.h"
29 : #include "dom/measurerepeat.h"
30 : #include "dom/part.h"
31 : #include "dom/playtechannotation.h"
32 : #include "dom/repeatlist.h"
33 : #include "dom/score.h"
34 : #include "dom/segment.h"
35 : #include "dom/soundflag.h"
36 : #include "dom/staff.h"
37 : #include "dom/stafftext.h"
38 :
39 : #include "engraving/automation/automationdata.h"
40 :
41 : #include "utils/arrangementutils.h"
42 : #include "utils/expressionutils.h"
43 : #include "types/constants.h"
44 :
45 : #include "global/containers.h"
46 : #include "global/realfn.h"
47 :
48 : using namespace mu::engraving;
49 : using namespace muse;
50 : using namespace muse::mpe;
51 :
52 9 : static bool soundFlagPlayable(const SoundFlag* flag)
53 : {
54 9 : if (flag && flag->play()) {
55 9 : return !flag->soundPresets().empty() || !flag->playingTechnique().empty();
56 : }
57 :
58 0 : return false;
59 : }
60 :
61 26 : static dynamic_level_t toDynamicLevel(double normalizedValue)
62 : {
63 26 : return static_cast<dynamic_level_t>(std::lround(normalizedValue * MAX_DYNAMIC_LEVEL));
64 : }
65 :
66 : template<typename ByTrackMap, typename ResultMap, typename MergeFn>
67 233 : static void collectByTrackRange(const ByTrackMap& byTrackMap, const track_idx_t trackFrom, const track_idx_t trackTo,
68 : const Score* score, const std::set<track_idx_t>& usedTracks, ResultMap& result, MergeFn merge)
69 : {
70 257 : for (auto it = byTrackMap.lower_bound(trackFrom); it != byTrackMap.end() && it->first < trackTo; ++it) {
71 24 : if (!muse::contains(usedTracks, it->first)) {
72 18 : continue;
73 : }
74 :
75 35 : for (const auto& pair : it->second) {
76 29 : const timestamp_t timestamp = timestampFromTicks(score, pair.first);
77 29 : merge(result[timestamp], pair.second);
78 : }
79 : }
80 233 : }
81 :
82 79 : PlaybackContext::PlaybackContext(const Score* score)
83 79 : : m_score(score)
84 : {
85 79 : }
86 :
87 1817 : dynamic_level_t PlaybackContext::appliableDynamicLevel(const track_idx_t trackIdx, const int nominalPositionTick) const
88 : {
89 1818 : TRACEFUNC;
90 :
91 1817 : const AutomationCurve* curve = dynamicsCurve(trackIdx);
92 1817 : if (!curve) {
93 0 : return NATURAL_DYNAMIC_LEVEL;
94 : }
95 :
96 1817 : const auto it = muse::findLessOrEqual(*curve, nominalPositionTick);
97 1817 : if (it == curve->cend()) {
98 1791 : return NATURAL_DYNAMIC_LEVEL;
99 : }
100 :
101 26 : const auto next = std::next(it);
102 26 : if (next == curve->cend()) {
103 10 : return toDynamicLevel(it->second.value.outValue);
104 : }
105 :
106 16 : const real_t t = static_cast<real_t>(nominalPositionTick - it->first) / static_cast<real_t>(next->first - it->first);
107 16 : return toDynamicLevel(muse::mpe::evaluateAt(next->second.value, it->second.value.outValue, t));
108 1817 : }
109 :
110 1840 : std::pair<mpe::timestamp_t, PlayingTechniqueType> PlaybackContext::playingTechnique(const track_idx_t trackIdx,
111 : const int nominalPositionTick) const
112 : {
113 1840 : auto mapIt = m_playTechniquesByTrack.find(trackIdx);
114 1840 : if (mapIt == m_playTechniquesByTrack.cend()) {
115 1669 : return std::make_pair(0, PlayingTechniqueType::Natural);
116 : }
117 :
118 171 : const PlayTechniquesMap& playTechniquesMap = mapIt->second;
119 :
120 171 : auto it = findLessOrEqual(playTechniquesMap, nominalPositionTick);
121 171 : if (it == playTechniquesMap.cend()) {
122 6 : return std::make_pair(0, PlayingTechniqueType::Natural);
123 : }
124 :
125 165 : return std::make_pair(timestampFromTicks(m_score, it->first), it->second);
126 : }
127 :
128 13 : muse::mpe::timestamp_t PlaybackContext::findPlayingTechniqueTimestamp(const track_idx_t trackIdx, PlayingTechniqueType type,
129 : const int startFromTick) const
130 : {
131 13 : auto mapIt = m_playTechniquesByTrack.find(trackIdx);
132 13 : if (mapIt == m_playTechniquesByTrack.cend()) {
133 0 : return -1;
134 : }
135 :
136 13 : const PlayTechniquesMap& playTechniquesMap = mapIt->second;
137 :
138 13 : auto it = playTechniquesMap.upper_bound(startFromTick);
139 :
140 13 : for (; it != playTechniquesMap.end(); ++it) {
141 5 : if (it->second == type) {
142 5 : return timestampFromTicks(m_score, it->first);
143 : }
144 : }
145 :
146 8 : return -1;
147 : }
148 :
149 76 : std::map<timestamp_t, SoundPresetChangeEventList> PlaybackContext::soundPresets(const track_idx_t trackFrom,
150 : const track_idx_t trackTo) const
151 : {
152 76 : std::map<timestamp_t, SoundPresetChangeEventList> result;
153 :
154 76 : collectByTrackRange(m_soundPresetsByTrack, trackFrom, trackTo, m_score, m_usedTracks, result,
155 0 : [](SoundPresetChangeEventList& list, const SoundPresetChangeEventList& events) {
156 0 : list.insert(list.end(), events.begin(), events.end());
157 0 : });
158 :
159 76 : return result;
160 0 : }
161 :
162 2 : SoundPresetChangeEventList PlaybackContext::soundPresets(const track_idx_t trackIdx, const int nominalPositionTick) const
163 : {
164 2 : auto presetsIt = m_soundPresetsByTrack.find(trackIdx);
165 2 : if (presetsIt == m_soundPresetsByTrack.end()) {
166 2 : return {};
167 : }
168 :
169 0 : const SoundPresetsMap& map = presetsIt->second;
170 0 : auto it = muse::findLessOrEqual(map, nominalPositionTick);
171 0 : if (it == map.end()) {
172 0 : return {};
173 : }
174 :
175 0 : return it->second;
176 : }
177 :
178 80 : std::map<timestamp_t, TextArticulationEventList> PlaybackContext::textArticulations(const track_idx_t trackFrom,
179 : const track_idx_t trackTo) const
180 : {
181 80 : std::map<timestamp_t, TextArticulationEventList> result;
182 :
183 80 : collectByTrackRange(m_textArticulationsByTrack, trackFrom, trackTo, m_score, m_usedTracks, result,
184 14 : [](TextArticulationEventList& list, const TextArticulationEvent& event) {
185 14 : list.push_back(event);
186 14 : });
187 :
188 80 : return result;
189 0 : }
190 :
191 34 : TextArticulationEvent PlaybackContext::textArticulation(const track_idx_t trackIdx, const int nominalPositionTick) const
192 : {
193 34 : auto articulationsIt = m_textArticulationsByTrack.find(trackIdx);
194 34 : if (articulationsIt == m_textArticulationsByTrack.end()) {
195 2 : return {};
196 : }
197 :
198 32 : const TextArticulationMap& map = articulationsIt->second;
199 32 : auto it = muse::findLessOrEqual(map, nominalPositionTick);
200 32 : if (it == map.end()) {
201 8 : return {};
202 : }
203 :
204 24 : TextArticulationEvent result = it->second;
205 24 : result.flags.setFlag(TextArticulationEvent::StartsAtPlaybackPosition, it->first == nominalPositionTick);
206 :
207 24 : return result;
208 34 : }
209 :
210 77 : std::map<timestamp_t, SyllableEventList> PlaybackContext::syllables(const track_idx_t trackFrom, const track_idx_t trackTo) const
211 : {
212 77 : std::map<timestamp_t, SyllableEventList> result;
213 :
214 77 : collectByTrackRange(m_syllablesByTrack, trackFrom, trackTo, m_score, m_usedTracks, result,
215 15 : [](SyllableEventList& list, const SyllableEvent& event) {
216 15 : list.push_back(event);
217 15 : });
218 :
219 77 : return result;
220 0 : }
221 :
222 2 : SyllableEvent PlaybackContext::syllable(const track_idx_t trackIdx, const int nominalPositionTick) const
223 : {
224 2 : auto syllablesIt = m_syllablesByTrack.find(trackIdx);
225 2 : if (syllablesIt == m_syllablesByTrack.end()) {
226 2 : return {};
227 : }
228 :
229 0 : const SyllableMap& map = syllablesIt->second;
230 0 : auto it = muse::findLessOrEqual(map, nominalPositionTick);
231 0 : if (it == map.end()) {
232 0 : return {};
233 : }
234 :
235 0 : SyllableEvent result = it->second;
236 0 : result.flags.setFlag(SyllableEvent::StartsAtPlaybackPosition, it->first == nominalPositionTick);
237 :
238 0 : return result;
239 2 : }
240 :
241 83 : DynamicAutomationLayers PlaybackContext::dynamicLevelLayers(const track_idx_t trackFrom, const track_idx_t trackTo) const
242 : {
243 84 : TRACEFUNC;
244 :
245 83 : if (!m_score || !m_score->automationData()) {
246 0 : return {};
247 : }
248 :
249 83 : DynamicAutomationLayers result;
250 83 : const AutomationCurve* lastCurve = nullptr;
251 83 : DynamicAutomationMap lastLevelMap;
252 :
253 451 : for (track_idx_t trackIdx = trackFrom; trackIdx < trackTo; ++trackIdx) {
254 368 : const AutomationCurve* curve = dynamicsCurve(trackIdx);
255 368 : if (!curve || curve->empty()) {
256 328 : continue;
257 : }
258 :
259 40 : if (curve != lastCurve) {
260 12 : lastLevelMap.clear();
261 12 : lastCurve = curve;
262 12 : auto hint = lastLevelMap.end();
263 :
264 : // AutomationCurve ticks are always in expanded utick space, regardless of the Play Repeats setting
265 12 : const TempoTimeline& expandedTimeline = m_score->tempoTimeline(/*expandRepeats*/ true);
266 :
267 12 : if (curve->cbegin()->first > 0) {
268 1 : AutomationPoint naturalPoint;
269 1 : naturalPoint.value.outValue = real_t(NATURAL_DYNAMIC_LEVEL) / real_t(MAX_DYNAMIC_LEVEL);
270 1 : hint = lastLevelMap.insert(hint, { expandedTimeline.utick2utime(0) * 1000000, naturalPoint.value });
271 : }
272 :
273 74 : for (const auto& [tick, point] : *curve) {
274 62 : hint = lastLevelMap.insert(hint, { expandedTimeline.utick2utime(tick) * 1000000, point.value });
275 : }
276 : }
277 :
278 40 : result[static_cast<layer_idx_t>(trackIdx)] = lastLevelMap;
279 : }
280 :
281 83 : return result;
282 83 : }
283 :
284 47 : void PlaybackContext::update(const track_idx_t trackFrom, const track_idx_t trackTo, const int tickFrom, const int tickTo,
285 : bool expandRepeats)
286 : {
287 48 : TRACEFUNC;
288 :
289 47 : IF_ASSERT_FAILED(m_score) {
290 0 : return;
291 : }
292 :
293 47 : IF_ASSERT_FAILED(trackFrom <= trackTo) {
294 0 : return;
295 : }
296 :
297 47 : m_dynamicsCurveByTrack.clear();
298 :
299 116 : for (const RepeatSegment* repeatSegment : m_score->repeatList(expandRepeats)) {
300 69 : const int repeatStartTick = repeatSegment->tick;
301 69 : const int repeatEndTick = repeatSegment->endTick();
302 :
303 69 : if (repeatStartTick > tickTo || repeatEndTick <= tickFrom) {
304 0 : continue;
305 : }
306 :
307 69 : std::vector<const MeasureRepeat*> measureRepeats;
308 69 : int tickPositionOffset = repeatSegment->utick - repeatSegment->tick;
309 :
310 330 : for (const Measure* measure : repeatSegment->measureList()) {
311 261 : const int measureStartTick = measure->tick().ticks();
312 261 : const int measureEndTick = measure->endTick().ticks();
313 :
314 261 : if (measureStartTick > tickTo || measureEndTick <= tickFrom) {
315 10 : continue;
316 : }
317 :
318 1614 : for (const Segment* segment = measure->first(); segment; segment = segment->next()) {
319 1368 : const int segmentTick = segment->tick().ticks();
320 1368 : if (segmentTick > tickTo) {
321 5 : break;
322 : }
323 :
324 1363 : if (segmentTick < tickFrom) {
325 16 : continue;
326 : }
327 :
328 1347 : int segmentStartTick = segmentTick + tickPositionOffset;
329 :
330 1347 : handleSegmentElements(repeatSegment, segment, segmentStartTick, trackFrom, trackTo, measureRepeats);
331 1347 : handleSegmentAnnotations(segment, segmentStartTick, trackFrom, trackTo);
332 : }
333 : }
334 :
335 69 : handleMeasureRepeats(measureRepeats, tickPositionOffset);
336 69 : }
337 47 : }
338 :
339 11 : void PlaybackContext::clear(const track_idx_t trackFrom, const track_idx_t trackTo, const int tickFrom, const int tickTo)
340 : {
341 55 : const auto eraseTickRangeByTrack = [trackFrom, trackTo, tickFrom, tickTo](auto& byTrackMap) {
342 59 : for (auto it = byTrackMap.lower_bound(trackFrom); it != byTrackMap.end() && it->first < trackTo;) {
343 4 : auto& innerMap = it->second;
344 4 : innerMap.erase(innerMap.lower_bound(tickFrom), innerMap.upper_bound(tickTo));
345 :
346 4 : if (innerMap.empty()) {
347 4 : it = byTrackMap.erase(it);
348 : } else {
349 0 : ++it;
350 : }
351 : }
352 66 : };
353 :
354 11 : eraseTickRangeByTrack(m_soundPresetsByTrack);
355 11 : eraseTickRangeByTrack(m_textArticulationsByTrack);
356 11 : eraseTickRangeByTrack(m_syllablesByTrack);
357 11 : eraseTickRangeByTrack(m_playTechniquesByTrack);
358 11 : eraseTickRangeByTrack(m_multiVerseLyricsPositionMap);
359 :
360 11 : muse::remove_if(m_currentVerseNumByChordRest, [trackFrom, trackTo, tickFrom, tickTo](const auto& pair) {
361 0 : const track_idx_t track = pair.first->track();
362 0 : const int tick = pair.first->tick().ticks();
363 0 : return track >= trackFrom && track < trackTo && tick >= tickFrom && tick <= tickTo;
364 : });
365 11 : }
366 :
367 0 : bool PlaybackContext::hasSoundFlags(const track_idx_t trackFrom, const track_idx_t trackTo) const
368 : {
369 0 : auto hasEntryInRange = [trackFrom, trackTo](const auto& byTrackMap) {
370 0 : auto it = byTrackMap.lower_bound(trackFrom);
371 0 : return it != byTrackMap.end() && it->first < trackTo;
372 0 : };
373 :
374 0 : return hasEntryInRange(m_soundPresetsByTrack) || hasEntryInRange(m_textArticulationsByTrack);
375 : }
376 :
377 36 : void PlaybackContext::updatePlayTechMap(const Part* part, const PlayTechAnnotation* annotation, const int segmentPositionTick)
378 : {
379 36 : if (!annotation->playPlayTechAnnotation()) {
380 0 : return;
381 : }
382 36 : const PlayingTechniqueType type = annotation->techniqueType();
383 36 : if (type == PlayingTechniqueType::Undefined) {
384 0 : return;
385 : }
386 :
387 34 : const bool cancelPlayTechniques = (type == PlayingTechniqueType::Natural || type == PlayingTechniqueType::Open)
388 70 : && !m_textArticulationsByTrack.empty();
389 :
390 36 : TextArticulationEvent textArticulation;
391 36 : textArticulation.text = mpe::ORDINARY_PLAYING_TECHNIQUE_CODE;
392 :
393 36 : const TrackRange trackRange = part->trackRange();
394 184 : for (track_idx_t idx = trackRange.startTrack; idx < trackRange.endTrack; ++idx) {
395 148 : m_playTechniquesByTrack[idx][segmentPositionTick] = type;
396 :
397 148 : if (cancelPlayTechniques) {
398 8 : textArticulation.layerIdx = static_cast<layer_idx_t>(idx);
399 8 : m_textArticulationsByTrack[idx][segmentPositionTick] = textArticulation;
400 : }
401 : }
402 36 : }
403 :
404 8 : void PlaybackContext::updateSoundPresetAndTextArticulationMap(const Part* part, const SoundFlagMap& flagsOnSegment,
405 : const int segmentPositionTick)
406 : {
407 52 : auto trackAccepted = [&flagsOnSegment](const SoundFlag* flag, track_idx_t trackIdx) {
408 52 : staff_idx_t staffIdx = track2staff(trackIdx);
409 :
410 52 : if (flag->staffIdx() == staffIdx) {
411 36 : return true;
412 : }
413 :
414 16 : if (flag->applyToAllStaves()) {
415 8 : return !muse::contains(flagsOnSegment, staffIdx);
416 : }
417 :
418 8 : return false;
419 8 : };
420 :
421 8 : const TrackRange trackRange = part->trackRange();
422 :
423 17 : for (const auto& pair : flagsOnSegment) {
424 9 : const SoundFlag* flag = pair.second;
425 :
426 61 : for (track_idx_t trackIdx = trackRange.startTrack; trackIdx < trackRange.endTrack; ++trackIdx) {
427 52 : if (!trackAccepted(flag, trackIdx)) {
428 12 : continue;
429 : }
430 :
431 40 : SoundPresetChangeEventList presets;
432 :
433 40 : for (const String& soundPreset : flag->soundPresets()) {
434 0 : if (soundPreset.empty()) {
435 0 : continue;
436 : }
437 :
438 0 : SoundPresetChangeEvent event;
439 0 : event.code = soundPreset;
440 0 : event.layerIdx = static_cast<layer_idx_t>(trackIdx);
441 0 : presets.emplace_back(std::move(event));
442 0 : }
443 :
444 40 : if (!presets.empty()) {
445 0 : SoundPresetChangeEventList& target = m_soundPresetsByTrack[trackIdx][segmentPositionTick];
446 0 : target.insert(target.end(), std::make_move_iterator(presets.begin()), std::make_move_iterator(presets.end()));
447 : }
448 :
449 40 : if (!flag->playingTechnique().empty()) {
450 40 : TextArticulationEvent event;
451 40 : event.text = flag->playingTechnique();
452 40 : event.layerIdx = static_cast<layer_idx_t>(trackIdx);
453 40 : m_textArticulationsByTrack[trackIdx][segmentPositionTick] = std::move(event);
454 40 : }
455 40 : }
456 :
457 9 : if (flag->playingTechnique() == mpe::ORDINARY_PLAYING_TECHNIQUE_CODE) {
458 5 : for (track_idx_t trackIdx = trackRange.startTrack; trackIdx < trackRange.endTrack; ++trackIdx) {
459 4 : m_playTechniquesByTrack[trackIdx][segmentPositionTick] = PlayingTechniqueType::Natural;
460 : }
461 : }
462 : }
463 8 : }
464 :
465 15 : void PlaybackContext::updateSyllableMap(const TextBase* text, const int segmentPositionTick)
466 : {
467 15 : IF_ASSERT_FAILED(text->isLyrics() || text->isSticking()) {
468 0 : return;
469 : }
470 :
471 15 : if (text->empty()) {
472 0 : return;
473 : }
474 :
475 15 : SyllableEvent syllable;
476 15 : syllable.text = text->plainText();
477 :
478 15 : if (text->isLyrics()) {
479 15 : const Lyrics* lyrics = toLyrics(text);
480 :
481 15 : switch (lyrics->syllabic()) {
482 2 : case LyricsSyllabic::BEGIN:
483 : case LyricsSyllabic::MIDDLE:
484 2 : syllable.flags.setFlag(SyllableEvent::HyphenedToNext);
485 2 : break;
486 13 : case LyricsSyllabic::SINGLE:
487 : case LyricsSyllabic::END:
488 13 : break;
489 : }
490 : }
491 :
492 15 : const staff_idx_t staffIdx = text->staffIdx();
493 :
494 75 : for (voice_idx_t voiceIdx = 0; voiceIdx < VOICES; ++voiceIdx) {
495 60 : track_idx_t trackIdx = staff2track(staffIdx, voiceIdx);
496 60 : syllable.layerIdx = static_cast<layer_idx_t>(trackIdx);
497 60 : m_syllablesByTrack[trackIdx][segmentPositionTick] = syllable;
498 : }
499 15 : }
500 :
501 1347 : void PlaybackContext::handleSegmentAnnotations(const Segment* segment, const int segmentPositionTick,
502 : const track_idx_t trackFrom, const track_idx_t trackTo)
503 : {
504 1347 : std::unordered_map<const Part*, SoundFlagMap> soundFlagsByPart;
505 :
506 1469 : for (const EngravingItem* annotation : segment->annotations()) {
507 122 : if (!annotation || !annotation->part()) {
508 49 : continue;
509 : }
510 :
511 122 : const Part* annotationPart = annotation->part();
512 122 : const TrackRange annotationPartTrackRange = annotationPart->trackRange();
513 122 : if (annotationPartTrackRange.startTrack >= trackTo || annotationPartTrackRange.endTrack <= trackFrom) {
514 13 : continue;
515 : }
516 :
517 109 : if (annotation->isPlayTechAnnotation()) {
518 36 : updatePlayTechMap(annotationPart, toPlayTechAnnotation(annotation), segmentPositionTick);
519 36 : continue;
520 : }
521 :
522 73 : if (annotation->isSticking()) {
523 0 : updateSyllableMap(toTextBase(annotation), segmentPositionTick);
524 0 : continue;
525 : }
526 :
527 73 : if (annotation->isStaffText()) {
528 20 : if (const SoundFlag* flag = toStaffText(annotation)->soundFlag()) {
529 9 : if (soundFlagPlayable(flag)) {
530 9 : soundFlagsByPart[annotationPart].emplace(flag->staffIdx(), flag);
531 : }
532 : }
533 : }
534 : }
535 :
536 1355 : for (const auto& pair : soundFlagsByPart) {
537 8 : const Part* part = pair.first;
538 8 : const SoundFlagMap& flagsOnSegment = pair.second;
539 :
540 8 : if (!flagsOnSegment.empty()) {
541 8 : updateSoundPresetAndTextArticulationMap(part, flagsOnSegment, segmentPositionTick);
542 : }
543 : }
544 1347 : }
545 :
546 1347 : void PlaybackContext::handleSegmentElements(const RepeatSegment* repeat, const Segment* segment,
547 : const int segmentPositionTick,
548 : const track_idx_t trackFrom, const track_idx_t trackTo,
549 : std::vector<const MeasureRepeat*>& foundMeasureRepeats)
550 : {
551 10127 : for (track_idx_t track = trackFrom; track < trackTo; ++track) {
552 8780 : const EngravingItem* item = segment->element(track);
553 8780 : if (!item) {
554 6561 : continue;
555 : }
556 :
557 2219 : if (item->isMeasureRepeat()) {
558 12 : foundMeasureRepeats.push_back(toMeasureRepeat(item));
559 12 : continue;
560 : }
561 :
562 2207 : if (item->isChordRest()) {
563 1388 : m_usedTracks.insert(track);
564 :
565 1388 : const ChordRest* chordRest = toChordRest(item);
566 1388 : if (chordRest->lyrics().empty()) {
567 1370 : continue;
568 : }
569 :
570 18 : const Lyrics* lyrics = nullptr;
571 :
572 18 : auto verseNumIt = m_currentVerseNumByChordRest.find(chordRest);
573 18 : if (verseNumIt == m_currentVerseNumByChordRest.end()) {
574 9 : m_currentVerseNumByChordRest[chordRest] = 0;
575 9 : lyrics = chordRest->lyrics(0);
576 9 : if (chordRest->lyrics().size() > 1) {
577 4 : m_multiVerseLyricsPositionMap[track].insert(chordRest->tick().ticks());
578 : }
579 9 : } else if (hasOnlyOneLyricsVerse(repeat, track)) {
580 2 : lyrics = chordRest->lyrics(0);
581 : } else {
582 7 : verseNumIt->second++;
583 7 : lyrics = chordRest->lyrics(verseNumIt->second);
584 : }
585 :
586 18 : if (lyrics) {
587 15 : updateSyllableMap(lyrics, segmentPositionTick);
588 : }
589 : }
590 : }
591 1347 : }
592 :
593 : template<typename ItemsMap>
594 16 : static void copyItemsInRange(ItemsMap& source, const int rangeStartTick, const int rangeEndTick, const int newItemsOffsetTick)
595 : {
596 16 : auto startIt = source.lower_bound(rangeStartTick);
597 16 : if (startIt == source.end()) {
598 0 : return;
599 : }
600 :
601 16 : auto endIt = source.lower_bound(rangeEndTick);
602 :
603 16 : ItemsMap newItems;
604 32 : for (auto it = startIt; it != endIt; ++it) {
605 16 : int tick = it->first + newItemsOffsetTick;
606 16 : newItems.insert_or_assign(tick, it->second);
607 : }
608 :
609 16 : source.merge(std::move(newItems));
610 16 : }
611 :
612 : template<typename ItemsMap>
613 76 : static void copyItemsInRange(std::map<track_idx_t, ItemsMap>& source, const track_idx_t trackFrom, const track_idx_t trackTo,
614 : const int rangeStartTick, const int rangeEndTick, const int newItemsOffsetTick)
615 : {
616 92 : for (auto it = source.lower_bound(trackFrom); it != source.end() && it->first < trackTo; ++it) {
617 16 : copyItemsInRange(it->second, rangeStartTick, rangeEndTick, newItemsOffsetTick);
618 : }
619 76 : }
620 :
621 69 : void PlaybackContext::handleMeasureRepeats(const std::vector<const MeasureRepeat*>& measureRepeats, const int tickPositionOffset)
622 : {
623 81 : for (const MeasureRepeat* mr : measureRepeats) {
624 12 : const Part* part = mr->part();
625 12 : if (!part) {
626 0 : continue;
627 : }
628 :
629 12 : const Measure* currMeasure = mr->firstMeasureOfGroup();
630 12 : if (!currMeasure) {
631 0 : continue;
632 : }
633 :
634 12 : const Measure* referringMeasure = mr->referringMeasure(currMeasure);
635 12 : if (!referringMeasure) {
636 0 : continue;
637 : }
638 :
639 12 : int currentMeasureTick = currMeasure->tick().ticks();
640 12 : int referringMeasureTick = referringMeasure->tick().ticks();
641 12 : int newItemsOffsetTick = currentMeasureTick - referringMeasureTick;
642 :
643 12 : const TrackRange trackRange = part->trackRange();
644 :
645 24 : for (int num = 0; num < mr->numMeasures(); ++num) {
646 19 : int startTick = referringMeasure->tick().ticks() + tickPositionOffset;
647 19 : int endTick = referringMeasure->endTick().ticks() + tickPositionOffset;
648 :
649 19 : copyItemsInRange(m_soundPresetsByTrack, trackRange.startTrack, trackRange.endTrack, startTick, endTick, newItemsOffsetTick);
650 19 : copyItemsInRange(m_textArticulationsByTrack, trackRange.startTrack, trackRange.endTrack, startTick, endTick,
651 : newItemsOffsetTick);
652 19 : copyItemsInRange(m_syllablesByTrack, trackRange.startTrack, trackRange.endTrack, startTick, endTick, newItemsOffsetTick);
653 19 : copyItemsInRange(m_playTechniquesByTrack, trackRange.startTrack, trackRange.endTrack, startTick, endTick,
654 : newItemsOffsetTick);
655 :
656 19 : currMeasure = currMeasure->nextMeasure();
657 19 : if (!currMeasure) {
658 5 : break;
659 : }
660 :
661 14 : referringMeasure = mr->referringMeasure(currMeasure);
662 14 : if (!referringMeasure) {
663 2 : break;
664 : }
665 : }
666 : }
667 69 : }
668 :
669 2185 : const mu::engraving::AutomationCurve* PlaybackContext::dynamicsCurve(const track_idx_t trackIdx) const
670 : {
671 2185 : auto cacheIt = m_dynamicsCurveByTrack.find(trackIdx);
672 2185 : if (cacheIt != m_dynamicsCurveByTrack.end()) {
673 1771 : return cacheIt->second;
674 : }
675 :
676 414 : const AutomationDataConstPtr automation = m_score ? m_score->automationData() : nullptr;
677 414 : const Staff* staff = automation ? m_score->staff(track2staff(trackIdx)) : nullptr;
678 :
679 414 : const AutomationCurve* curve = nullptr;
680 414 : if (staff) {
681 414 : AutomationCurveKey key = AutomationCurveKey::staff(AutomationType::Dynamics, staff->id(), track2voice(trackIdx));
682 414 : curve = &automation->curve(key);
683 :
684 414 : if (curve->empty()) {
685 412 : key = key.withoutVoice();
686 412 : curve = &automation->curve(key);
687 : }
688 414 : }
689 :
690 414 : m_dynamicsCurveByTrack.emplace(trackIdx, curve);
691 414 : return curve;
692 414 : }
693 :
694 9 : bool PlaybackContext::hasOnlyOneLyricsVerse(const RepeatSegment* repeat, const track_idx_t track) const
695 : {
696 9 : if (m_multiVerseLyricsPositionMap.empty()) {
697 0 : return true;
698 : }
699 :
700 9 : const auto trackIt = m_multiVerseLyricsPositionMap.find(track);
701 9 : if (trackIt == m_multiVerseLyricsPositionMap.cend()) {
702 0 : return true;
703 : }
704 :
705 9 : const int startTick = repeat->tick;
706 9 : const int endTick = repeat->endTick();
707 9 : const auto start = trackIt->second.lower_bound(startTick);
708 9 : const auto end = trackIt->second.lower_bound(endTick);
709 :
710 9 : return start == end;
711 : }
|