LCOV - code coverage report
Current view: top level - src/importexport/tabledit/internal - readinglist.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 93.9 % 82 77
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 "readinglist.h"
      24              : 
      25              : namespace mu::iex::tabledit {
      26           13 : bool MeasureStatus::isRegular()
      27              : {
      28           13 :     if (barlineDouble || barlineEnd || repeatStart || repeatEnd) {
      29            6 :         return false;
      30              :     }
      31              : 
      32            7 :     return true;
      33              : }
      34              : 
      35              : // return the number of exact matches at the front of the list
      36              : // due to compaction, this is rare
      37              : 
      38           18 : size_t findExactMatches(const std::vector<SimplifiedItem>& list)
      39              : {
      40           18 :     if (list.size() == 0) {
      41            0 :         return 0; // shouldn't happen
      42              :     }
      43           18 :     size_t res { 1 };
      44           25 :     while (res < list.size() && list.at(0) == list.at(res)) {
      45            7 :         ++res;
      46              :     }
      47           18 :     return res;
      48              : }
      49              : 
      50              : // return the number of exact matches at the second items on the list
      51              : 
      52            2 : size_t findExactMatchesFrom2nd(const std::vector<SimplifiedItem>& list)
      53              : {
      54            2 :     if (list.size() == 0) {
      55            0 :         return 0; // shouldn't happen
      56              :     }
      57            2 :     size_t res { 1 };
      58            2 :     while (res + 1 < list.size() && list.at(1) == list.at(res + 1)) {
      59            0 :         ++res;
      60              :     }
      61            2 :     return res;
      62              : }
      63              : 
      64              : // copy all valid TefReadingListItems to m_list
      65              : // with the measure numbers changed from one-based to zero-based
      66              : // and items describing consecutive sets of measures merged
      67              : 
      68           61 : void ReadingList::initializeList(const size_t nMeasures, const std::vector<TefReadingListItem>& tefReadingList)
      69              : {
      70              :     UNUSED(nMeasures);
      71           92 :     for (const auto& tefItem : tefReadingList) {
      72              :         if (/* todo: item is valid */ true) {
      73           31 :             SimplifiedItem i;
      74           31 :             i.first = tefItem.firstMeasure - 1;
      75           31 :             i.last = tefItem.lastMeasure - 1;
      76           31 :             if (m_list.empty()) {
      77              :                 // just add
      78           13 :                 m_list.push_back(i);
      79           18 :             } else if (m_list.back().isConsecutiveWith(i)) {
      80            2 :                 m_list.back() += i;
      81              :             } else {
      82              :                 // just add
      83           16 :                 m_list.push_back(i);
      84              :             }
      85              :         }
      86              :         LOGN("items");
      87           86 :         for (auto i : m_list) {
      88              :             LOGN("first %d last %d", i.first + 1, i.last + 1);
      89              :         }
      90              :     }
      91           61 : }
      92              : 
      93           61 : void ReadingList::calculate(const size_t nMeasures, const std::vector<TefReadingListItem>& tefReadingList)
      94              : {
      95              :     LOGN("reading list size %zu number of measures %zu", tefReadingList.size(), nMeasures);
      96           61 :     if (nMeasures == 0) {
      97            0 :         return;
      98              :     }
      99           61 :     initializeList(nMeasures, tefReadingList);
     100           61 :     m_status.resize(nMeasures);
     101          202 :     for (const auto& stat : m_status) {
     102              :         LOGN("1 repeatEnd %d repeatStart %d", stat.repeatEnd, stat.repeatStart);
     103              :     }
     104           61 :     if (!m_list.empty()) {
     105           13 :         analyze();
     106              :     }
     107          202 :     for (const auto& stat : m_status) {
     108              :         LOGN("2 repeatEnd %d repeatStart %d", stat.repeatEnd, stat.repeatStart);
     109              :     }
     110              : }
     111              : 
     112           13 : void ReadingList::analyze()
     113              : {
     114              :     // todo: check handling single remaining segment
     115           31 :     while (m_list.size()) {
     116           18 :         size_t itemsUsed { findExactMatches(m_list) };
     117              :         LOGN("findExactMatches() count %zu", itemsUsed);
     118           18 :         bool overlapWithNext { m_list.size() > itemsUsed && m_list.at(0).overlapsWith(m_list.at(itemsUsed)) };
     119              :         LOGN("overlapWithNext %s", (overlapWithNext ? "true" : "false"));
     120           18 :         if (itemsUsed > 1) {
     121            4 :             m_status[m_list.at(0).first].repeatStart = true;
     122            4 :             m_status[m_list.at(itemsUsed - 1).last].repeatEnd = true;
     123            4 :             if (itemsUsed > 2) {
     124            2 :                 m_status[m_list.at(itemsUsed - 1).last].repeatCount = static_cast<int>(itemsUsed);
     125              :             }
     126            4 :             m_list.erase(m_list.begin(), m_list.begin() + itemsUsed);
     127            4 :             continue;
     128              :         }
     129              : 
     130           14 :         if (m_list.size() >= 2 && m_list.at(0).overlapsWith(m_list.at(1))) {
     131              :             LOGN("overlap %d-%d %d-%d", m_list.at(0).first, m_list.at(0).last, m_list.at(1).first, m_list.at(1).last);
     132            7 :             if (m_list.at(0).first <= m_list.at(1).first) {
     133            7 :                 m_status[m_list.at(1).first].repeatStart = true;
     134              :             } else {
     135              :                 // TableEdit:
     136              :                 // starts playing at list.at(1).first
     137              :                 // repeats from list.at(0).first
     138              :                 // but does not show where to start playing
     139              :                 // MuseScore: tbd
     140              :                 // test file 010.tef
     141              :                 LOGN("overlap: second starts before first");
     142            0 :                 m_status[m_list.at(1).first].repeatStart = true;
     143              :             }
     144            7 :             if (m_list.at(0).last < m_list.at(1).last) {
     145            3 :                 m_status[m_list.at(0).last].repeatEnd = true;
     146            3 :                 itemsUsed = 1;
     147            4 :             } else if (m_list.at(0).last == m_list.at(1).last) {
     148            2 :                 m_status[m_list.at(0).last].repeatEnd = true;
     149            2 :                 itemsUsed = 2;
     150              :             } else {
     151              :                 // todo: how to handle at(0) not consecutive with at(2) ?
     152              :                 // todo: handle multiple repeats of first ending (alternative_ending_2a, 4b and 4d)
     153              :                 // -> first measure in ending 1 gets count = #repeats - 1
     154              :                 // todo: handle multiple repeats of second ending (alternative_ending_2b and 4e)
     155              :                 // -> first measure in ending 2 gets count = #repeats - 1
     156              :                 LOGN("overlap: second ends before first");
     157            2 :                 if (m_list.size() > 2 && m_list.at(0).isConsecutiveWith(m_list.at(2))) {
     158              :                     // alternative ending
     159            2 :                     m_status[m_list.at(0).last].repeatEnd = true;
     160            2 :                     Ending ending2;
     161            2 :                     ending2.number = 2;
     162            2 :                     m_status[m_list.at(2).first].ending = ending2;
     163            2 :                     const int measuresInEnding1 { m_list.at(0).last - m_list.at(1).last };
     164            2 :                     const size_t ending1Repeats { findExactMatchesFrom2nd(m_list) };
     165              :                     LOGN("ending 1 end %d measures %d repeats %zu", m_list.at(0).last, measuresInEnding1, ending1Repeats);
     166            2 :                     Ending ending1;
     167            2 :                     ending1.duration = measuresInEnding1;
     168            2 :                     ending1.number = 1;
     169            2 :                     m_status[m_list.at(0).last - measuresInEnding1 + 1].ending = ending1;
     170              :                 }
     171            2 :                 itemsUsed = 2;
     172              :             }
     173            7 :             m_list.erase(m_list.begin(), m_list.begin() + itemsUsed);
     174            7 :             continue;
     175            7 :         }
     176              : 
     177            7 :         itemsUsed = 1;
     178            7 :         m_list.erase(m_list.begin(), m_list.begin() + itemsUsed);
     179            7 :         continue;
     180            7 :     }
     181           13 :     if (m_status.back().isRegular()) {
     182            7 :         m_status.back().barlineEnd = true;
     183              :     }
     184           13 : }
     185              : } // namespace mu::iex::tabledit
        

Generated by: LCOV version 2.0-1