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 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 : #include "sharedpart.h"
23 : #include "score.h"
24 : #include "staff.h"
25 :
26 : #include <sstream>
27 :
28 : namespace mu::engraving {
29 0 : std::string dump(const SharedTrackMap& map)
30 : {
31 0 : std::stringstream ss;
32 0 : ss << "SharedTrackMap size: " << map.size() << " {";
33 0 : for (auto it = map.begin(); it != map.end(); ++it) {
34 0 : if (it != map.begin()) {
35 0 : ss << ", ";
36 : }
37 0 : ss << it->first << ": " << it->second;
38 : }
39 0 : ss << "}";
40 0 : return ss.str();
41 0 : }
42 :
43 11 : SharedPart::SharedPart(Score* score)
44 44 : : Part(score, ElementType::SHARED_PART)
45 : {
46 11 : }
47 :
48 26 : void SharedPart::addOriginPart(Part* p)
49 : {
50 26 : DO_ASSERT(p->type() == ElementType::PART && !muse::contains(m_originParts, p));
51 :
52 26 : const std::vector<Part*>& parts = score()->parts();
53 26 : if (muse::contains(parts, p)) {
54 24 : std::unordered_map<Part*, size_t> order;
55 96 : for (size_t i = 0; i < parts.size(); ++i) {
56 72 : order[parts[i]] = i;
57 : }
58 :
59 24 : auto it = std::lower_bound(m_originParts.begin(), m_originParts.end(), p, [&order](Part* part1, Part* part2) {
60 12 : return order[part1] < order[part2];
61 : });
62 :
63 24 : m_originParts.insert(it, p);
64 24 : } else {
65 : // Can happen while reading files as p has not been added to the score yet
66 2 : m_originParts.push_back(p);
67 : }
68 :
69 26 : p->setSharedPart(this);
70 :
71 26 : computeIsSameInstruments();
72 26 : }
73 :
74 12 : void SharedPart::removeOriginPart(Part* p)
75 : {
76 12 : DO_ASSERT(muse::remove(m_originParts, p));
77 :
78 12 : p->setSharedPart(nullptr);
79 :
80 12 : computeIsSameInstruments();
81 12 : }
82 :
83 2832 : Part* SharedPart::mappedOriginPart(track_idx_t sharedTrack, const Fraction& tick) const
84 : {
85 2832 : const SharedTrackMap& trackMap = trackMapAtTick(tick.negative() ? Fraction(0, 1) : tick);
86 :
87 2832 : track_idx_t originTrack = muse::key(trackMap, sharedTrack, muse::nidx);
88 :
89 2832 : const Staff* originStaff = originTrack != muse::nidx ? score()->staff(track2staff(originTrack)) : nullptr;
90 2832 : return originStaff ? originStaff->part() : nullptr;
91 : }
92 :
93 2595 : const Part* SharedPart::originPart(track_idx_t sharedTrack, const Fraction& tick) const
94 : {
95 2595 : if (sharedTrack != muse::nidx) {
96 2595 : if (const Part* p = mappedOriginPart(sharedTrack, tick)) {
97 1710 : return p;
98 : }
99 : }
100 :
101 885 : return m_originParts.empty() ? this : m_originParts.front();
102 : }
103 :
104 237 : Part* SharedPart::originPartForStaff(staff_idx_t sharedStaffIdx, const Fraction& tick) const
105 : {
106 237 : return mappedOriginPart(staff2track(sharedStaffIdx, 0), tick);
107 : }
108 :
109 3688 : const SharedTrackMap& SharedPart::trackMapAtTick(const Fraction& tick) const
110 : {
111 : static constexpr Fraction TICK_ZERO = Fraction(0, 1);
112 :
113 3688 : IF_ASSERT_FAILED(m_trackMapsByTick.size() > 0) {
114 0 : const_cast<SharedPart*>(this)->m_trackMapsByTick[TICK_ZERO] = SharedTrackMap();
115 0 : return m_trackMapsByTick.at(TICK_ZERO);
116 : }
117 :
118 3688 : DO_ASSERT(muse::contains(m_trackMapsByTick, TICK_ZERO));
119 :
120 3688 : IF_ASSERT_FAILED(tick.positive()) {
121 0 : return m_trackMapsByTick.at(TICK_ZERO);
122 : }
123 :
124 3688 : auto upperBound = m_trackMapsByTick.upper_bound(tick);
125 3688 : DO_ASSERT(upperBound != m_trackMapsByTick.begin());
126 :
127 3688 : --upperBound;
128 :
129 3688 : return upperBound->second;
130 : }
131 :
132 351 : void SharedPart::setTrackMapAtTick(const SharedTrackMap& map, const Fraction& tick)
133 : {
134 351 : m_trackMapsByTick[tick] = map;
135 351 : }
136 :
137 351 : void mu::engraving::SharedPart::removeMapsBetweenTicks(const Fraction& startTick, const Fraction& endTick)
138 : {
139 351 : auto upperBound = m_trackMapsByTick.lower_bound(startTick);
140 351 : if (upperBound == m_trackMapsByTick.end()) {
141 19 : return;
142 : }
143 :
144 668 : for (auto iter = upperBound; iter != m_trackMapsByTick.end();) {
145 452 : if (iter->first >= endTick) {
146 116 : break;
147 : }
148 336 : iter = m_trackMapsByTick.erase(iter);
149 : }
150 : }
151 :
152 161 : bool SharedPart::isSameInstrumentsAtTick(const Fraction& tick)
153 : {
154 161 : if (m_originParts.empty()) {
155 6 : return false;
156 : }
157 :
158 155 : const Instrument* instr = m_originParts.front()->instrument(tick);
159 437 : for (Part* p : m_originParts) {
160 282 : if (p->instrument(tick)->id() != instr->id()) {
161 0 : return false;
162 : }
163 : }
164 :
165 155 : return true;
166 : }
167 :
168 38 : void SharedPart::computeIsSameInstruments()
169 : {
170 38 : m_isSameInstruments = isSameInstrumentsAtTick(Fraction(0, 1));
171 38 : }
172 :
173 0 : mu::engraving::String mu::engraving::SharedPart::partName() const
174 : {
175 0 : const Instrument* i = instrument();
176 0 : String fullName = i->longName();
177 :
178 0 : const String& transp = i->transposition();
179 0 : if (!transp.empty()) {
180 : //: For instrument transposition, e.g. Horn in F
181 0 : fullName += u" " + muse::mtrc("engraving", "in") + u" " + transp;
182 : }
183 :
184 0 : int firstNumber = 10000;
185 0 : int lastNumber = 0;
186 :
187 0 : for (const Part* originPart : originParts()) {
188 0 : int number = originPart->instrument()->number();
189 0 : firstNumber = std::min(firstNumber, number);
190 0 : lastNumber = std::max(lastNumber, number);
191 : }
192 :
193 0 : if (firstNumber == lastNumber) {
194 0 : return fullName;
195 : }
196 :
197 0 : fullName += u" " + String::number(firstNumber) + u"-" + String::number(lastNumber);
198 :
199 0 : return fullName;
200 0 : }
201 :
202 2 : PropertyValue SharedPart::getProperty(Pid pid) const
203 : {
204 2 : switch (pid) {
205 1 : case Pid::SHARED_PART_ENABLED:
206 1 : return m_enabled;
207 1 : default:
208 1 : return Part::getProperty(pid);
209 : }
210 : }
211 :
212 0 : PropertyValue SharedPart::propertyDefault(Pid pid) const
213 : {
214 0 : switch (pid) {
215 0 : case Pid::SHARED_PART_ENABLED:
216 0 : return true;
217 0 : default:
218 0 : return Part::propertyDefault(pid);
219 : }
220 : }
221 :
222 1 : bool SharedPart::setProperty(Pid pid, const PropertyValue& v)
223 : {
224 1 : switch (pid) {
225 1 : case Pid::SHARED_PART_ENABLED:
226 1 : m_enabled = v.toBool();
227 1 : break;
228 0 : default:
229 0 : return Part::setProperty(pid, v);
230 : }
231 :
232 1 : score()->setLayoutAll();
233 1 : return true;
234 : }
235 :
236 31996 : bool SharedPart::enabled() const
237 : {
238 31996 : return m_enabled && style().styleB(Sid::enableStaveSharing);
239 : }
240 :
241 36002 : bool SharedPart::show() const
242 : {
243 36002 : return m_show && m_enabled && style().styleB(Sid::enableStaveSharing);
244 : }
245 : }
|