LCOV - code coverage report
Current view: top level - src/importexport/tabledit/internal - measurehandler.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 96.0 % 101 97
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) 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
        

Generated by: LCOV version 2.0-1