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) 2025 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 <string>
24 :
25 : #include "global/containers.h"
26 :
27 : #include "measurehandler.h"
28 : #include "note.h"
29 :
30 : using namespace mu::engraving;
31 : namespace mu::iex::tabledit {
32 : //---------------------------------------------------------
33 : // MeasureHandler
34 : // Calculate measure-level timing corrections for pickup measures
35 : // A TablEdit pickup measure contains gaps at either the left or right side,
36 : // that must be removed. Also the measure's actual size must be calculated.
37 : //---------------------------------------------------------
38 :
39 : static int nominalSize(const std::vector<TefMeasure>& tefMeasures, const size_t idx);
40 :
41 : // debug support
42 :
43 427 : static void dumpIntVec(const char* name, const std::vector<int>& gaps)
44 : {
45 854 : std::string s { name };
46 1414 : for (const auto gap : gaps) {
47 987 : s += ' ';
48 987 : s += std::to_string(gap);
49 : }
50 : LOGN("%s", s.c_str());
51 427 : }
52 :
53 61 : void MeasureHandler::dumpActualsAndSumGaps(const std::vector<TefMeasure>& tefMeasures) const
54 : {
55 61 : std::vector<int> actuals;
56 202 : for (unsigned int i = 0; i < tefMeasures.size(); ++i) {
57 141 : actuals.push_back(actualSize(tefMeasures, i));
58 : }
59 61 : dumpIntVec("actuals", actuals);
60 :
61 61 : std::vector<int> sumGaps;
62 202 : for (unsigned int i = 0; i < tefMeasures.size(); ++i) {
63 141 : sumGaps.push_back(sumPreviousGaps(i));
64 : }
65 61 : dumpIntVec("sumGaps", sumGaps);
66 61 : }
67 :
68 61 : static void dumpNominals(const std::vector<TefMeasure>& tefMeasures)
69 : {
70 61 : std::vector<int> nominals;
71 202 : for (unsigned int i = 0; i < tefMeasures.size(); ++i) {
72 141 : nominals.push_back(nominalSize(tefMeasures, i));
73 : }
74 61 : dumpIntVec("nominals", nominals);
75 61 : }
76 :
77 61 : static void dumpPickups(const std::vector<TefMeasure>& tefMeasures)
78 : {
79 61 : std::vector<int> pickups;
80 202 : for (unsigned int i = 0; i < tefMeasures.size(); ++i) {
81 141 : pickups.push_back(tefMeasures.at(i).isPickup ? 1 : 0);
82 : }
83 61 : dumpIntVec("pickups", pickups);
84 61 : }
85 :
86 : // return the nominal size of measure idx, based on time signature
87 :
88 564 : static int nominalSize(const std::vector<TefMeasure>& tefMeasures, const size_t idx)
89 : {
90 564 : return 64 * tefMeasures.at(idx).numerator / tefMeasures.at(idx).denominator;
91 : }
92 :
93 : // return the actual size of measure idx
94 : // normal measure: based on time signature
95 : // pickup measure: based on time signature and left and right gaps
96 :
97 282 : int MeasureHandler::actualSize(const std::vector<TefMeasure>& tefMeasures, const size_t idx) const
98 : {
99 282 : int size { nominalSize(tefMeasures, idx) };
100 282 : if (tefMeasures.at(idx).isPickup) {
101 4 : size -= gapsLeft.at(idx) + gapsRight.at(idx);
102 : }
103 : LOGN("idx %zu size %d", idx, size);
104 282 : return size;
105 : }
106 :
107 61 : void MeasureHandler::initializeMeasureStartsAndGaps(const std::vector<TefMeasure>& tefMeasures)
108 : {
109 61 : int measureStart { 0 };
110 202 : for (size_t i = 0; i < tefMeasures.size(); ++i) {
111 141 : nominalMeasureStarts.push_back(measureStart);
112 141 : int measureSize { nominalSize(tefMeasures, i) };
113 141 : measureStart += measureSize;
114 : // also initialize the gaps
115 : // normal measure: set to 0 to ignore gaps
116 : // pickup measure: set to the measure size (to be corrected later when a smaller gap is found)
117 141 : const int gap { tefMeasures.at(i).isPickup ? measureSize : 0 };
118 141 : gapsLeft.push_back(gap);
119 141 : gapsRight.push_back(gap);
120 : }
121 61 : }
122 :
123 : // return the index of the measure containing tstart
124 : // note O2 behaviour in score size
125 :
126 1228 : size_t MeasureHandler::measureIndex(int tstart, const std::vector<TefMeasure>& tefMeasures) const
127 : {
128 2744 : for (size_t i = 0; i < tefMeasures.size(); ++i) {
129 2744 : auto start { nominalMeasureStarts.at(i) };
130 2744 : auto size { 64 * tefMeasures.at(i).numerator / tefMeasures.at(i).denominator };
131 2744 : if (start <= tstart && tstart < start + size) {
132 1228 : return i;
133 : }
134 : }
135 0 : return muse::nidx; // not found
136 : }
137 :
138 : // return the offset of tstart (distance from its measure's start)
139 :
140 4 : int MeasureHandler::offsetInMeasure(int tstart, const std::vector<TefMeasure>& tefMeasures)
141 : {
142 4 : size_t index { measureIndex(tstart, tefMeasures) };
143 4 : if (index >= tefMeasures.size()) {
144 0 : return -1; // not found
145 : }
146 4 : return tstart - nominalMeasureStarts.at(index);
147 : }
148 :
149 : // find the smallest offset of any note in a pickup measure
150 :
151 421 : void MeasureHandler::updateGapLeft(std::vector<int>& gapLeft, const int position, const std::vector<TefMeasure>& tefMeasures)
152 : {
153 421 : size_t index { measureIndex(position, tefMeasures) };
154 421 : if (index >= tefMeasures.size()) {
155 0 : return; // not found
156 : }
157 421 : if (tefMeasures.at(index).isPickup) {
158 2 : auto offset { offsetInMeasure(position, tefMeasures) };
159 2 : if (0 <= offset) {
160 2 : if (offset < gapLeft[index]) {
161 2 : gapLeft[index] = offset;
162 : }
163 : }
164 : }
165 : }
166 :
167 : // find the largest end time of any note in a pickup measure
168 :
169 421 : void MeasureHandler::updateGapRight(std::vector<int>& gapRight, const TefNote& note, const std::vector<TefMeasure>& tefMeasures)
170 : {
171 421 : auto pos { note.position };
172 421 : size_t index { measureIndex(pos, tefMeasures) };
173 421 : if (index >= tefMeasures.size()) {
174 0 : return; // not found
175 : }
176 421 : if (tefMeasures.at(index).isPickup) {
177 2 : auto offset { offsetInMeasure(pos, tefMeasures) };
178 : LOGN("pos %d index %zu offset %d", pos, index, offset);
179 2 : if (0 <= offset) {
180 2 : auto dur { durationToInt(note.duration) };
181 2 : auto end { offset + dur };
182 2 : auto size { 64 * tefMeasures.at(index).numerator / tefMeasures.at(index).denominator };
183 2 : auto gap { size - end };
184 : LOGN("dur %d end %d size %d gap %d", dur, end, size, gap);
185 2 : if (gap < gapRight[index]) {
186 2 : gapRight[index] = gap;
187 : }
188 : }
189 : }
190 : }
191 :
192 : // start time correction to be subtracted from note position due to gaps:
193 : // sum of gaps in previous measure(s) plus left gap in current measure
194 : // note only pickup measures count, regular measures always have gaps set to 0
195 :
196 523 : int MeasureHandler::sumPreviousGaps(const size_t idx) const
197 : {
198 523 : auto corr { 0 };
199 1113 : for (unsigned int j = 0; j < idx; ++j) {
200 590 : corr += gapsLeft.at(j) + gapsRight.at(j);
201 : }
202 523 : corr += gapsLeft.at(idx);
203 : LOGN("idx %zu corr %d", idx, corr);
204 523 : return corr;
205 : }
206 :
207 61 : void MeasureHandler::updateGaps(const std::vector<TefNote>& tefContents, const std::vector<TefMeasure>& tefMeasures)
208 : {
209 482 : for (const TefNote& note : tefContents) {
210 421 : updateGapLeft(gapsLeft, note.position, tefMeasures);
211 421 : updateGapRight(gapsRight, note, tefMeasures);
212 : }
213 61 : }
214 :
215 61 : void MeasureHandler::calculate(const std::vector<TefNote>& tefContents, const std::vector<TefMeasure>& tefMeasures)
216 : {
217 61 : initializeMeasureStartsAndGaps(tefMeasures);
218 61 : updateGaps(tefContents, tefMeasures);
219 :
220 : // debug: dump result
221 61 : dumpIntVec("starts", nominalMeasureStarts);
222 61 : dumpIntVec("gapsLeft", gapsLeft);
223 61 : dumpIntVec("gapsRight", gapsRight);
224 61 : dumpNominals(tefMeasures);
225 61 : dumpPickups(tefMeasures);
226 61 : dumpActualsAndSumGaps(tefMeasures);
227 61 : }
228 : } // namespace mu::iex::tabledit
|