LCOV - code coverage report
Current view: top level - src/engraving/editing - editautomationpoints.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 80.0 % 70 56
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) 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              : 
      23              : #include "editautomationpoints.h"
      24              : 
      25              : #include "engraving/automation/internal/scoreautomationcontroller.h"
      26              : 
      27              : #include "engraving/dom/measure.h"
      28              : #include "engraving/dom/repeatlist.h"
      29              : #include "engraving/dom/staff.h"
      30              : 
      31              : #include "log.h"
      32              : 
      33              : using namespace mu::engraving;
      34              : 
      35            4 : static std::optional<ChangedRange> computeChangedRange(const Score* score, const AutomationCurveKey& key,
      36              :                                                        const std::map<utick_t, std::optional<AutomationPoint> >& pointStates)
      37              : {
      38            4 :     IF_ASSERT_FAILED(score && !pointStates.empty()) {
      39            0 :         return std::nullopt;
      40              :     }
      41              : 
      42            4 :     size_t staffIdx = muse::nidx;
      43            4 :     if (const std::optional<muse::ID> staffId = key.staffId()) {
      44            4 :         for (size_t i = 0; i < score->nstaves(); ++i) {
      45            4 :             const Staff* staff = score->staff(i);
      46            4 :             if (staff && staff->id() == *staffId) {
      47            4 :                 staffIdx = i;
      48            4 :                 break;
      49              :             }
      50              :         }
      51              : 
      52            4 :         IF_ASSERT_FAILED(staffIdx != muse::nidx) {
      53            0 :             return std::nullopt;
      54              :         }
      55              :     }
      56              :     // else: Global/Instrument-scoped key - not tied to any staff, leave staffIdx as muse::nidx
      57              : 
      58            4 :     const RepeatList& repeatList = score->expandedRepeatList();
      59              : 
      60              :     //! NOTE: utick2tick can be non-monotonic across repeat passes (e.g. D.S. al Coda),
      61              :     //! so the base-tick extremes aren't necessarily at the utick extremes
      62            4 :     Fraction tickFrom = Fraction::fromTicks(repeatList.utick2tick(pointStates.begin()->first));
      63            4 :     Fraction tickTo = tickFrom;
      64            8 :     for (const auto& state : pointStates) {
      65            4 :         const Fraction tick = Fraction::fromTicks(repeatList.utick2tick(state.first));
      66            4 :         if (tick < tickFrom) {
      67            0 :             tickFrom = tick;
      68              :         }
      69            4 :         if (tick > tickTo) {
      70            0 :             tickTo = tick;
      71              :         }
      72              :     }
      73              : 
      74            4 :     return ChangedRange { tickFrom, tickTo, staffIdx, staffIdx };
      75              : }
      76              : 
      77            4 : static void widenChangedRange(const Score* score, const AutomationCurveKey& key, const AutomationPointEdits& edits,
      78              :                               ChangedRange& changedRange)
      79              : {
      80            4 :     if (key.type == AutomationType::Tempo) {
      81            0 :         const Measure* lastMeasure = score->lastMeasure();
      82            0 :         if (lastMeasure) {
      83            0 :             changedRange.tickTo = lastMeasure->endTick();
      84              :         }
      85            0 :         return;
      86              :     }
      87              : 
      88            4 :     const RepeatList& repeatList = score->expandedRepeatList();
      89            8 :     for (const AutomationPointEdit& edit : edits) {
      90            4 :         const Fraction tick = Fraction::fromTicks(repeatList.utick2tick(edit.tick));
      91            4 :         changedRange.tickFrom = std::min(changedRange.tickFrom, tick);
      92            4 :         changedRange.tickTo = std::max(changedRange.tickTo, tick);
      93              :     }
      94              : }
      95              : 
      96            1 : EditAutomationPoints::EditAutomationPoints(Score* score, ScoreAutomationController* controller,
      97            1 :                                            const AutomationCurveKey& key, const AutomationPointEdits& edits)
      98            1 :     : m_score(score), m_controller(controller), m_key(key)
      99              : {
     100            1 :     assert(score && controller && !edits.empty());
     101              : 
     102              :     // Collapse the edit list into the final value to write at each touched tick,
     103              :     // replaying the same erase-then-write order AutomationData::editPoints itself would apply
     104            2 :     for (const AutomationPointEdit& edit : edits) {
     105            1 :         if (const auto* setPoint = std::get_if<AutomationPointEdit::SetPoint>(&edit.change)) {
     106            1 :             m_pointStates[edit.tick] = setPoint->point;
     107            0 :         } else if (const auto* movePoint = std::get_if<AutomationPointEdit::MovePoint>(&edit.change)) {
     108            0 :             if (movePoint->from != edit.tick) {
     109            0 :                 m_pointStates[movePoint->from] = std::nullopt;
     110              :             }
     111            0 :             m_pointStates[edit.tick] = movePoint->point;
     112              :         } else {
     113            0 :             m_pointStates[edit.tick] = std::nullopt;
     114              :         }
     115              :     }
     116            1 : }
     117              : 
     118            4 : std::optional<ChangedRange> EditAutomationPoints::changedRange() const
     119              : {
     120            4 :     return m_changedRange;
     121              : }
     122              : 
     123            4 : void EditAutomationPoints::flip()
     124              : {
     125            5 :     TRACEFUNC;
     126              : 
     127            4 :     IF_ASSERT_FAILED(m_score && m_controller && m_controller->automationData()) {
     128            0 :         return;
     129              :     }
     130              : 
     131            4 :     const AutomationCurve& curve = m_controller->automationData()->curve(m_key);
     132            4 :     m_changedRange = computeChangedRange(m_score, m_key, m_pointStates);
     133              : 
     134            4 :     std::map<utick_t, std::optional<AutomationPoint> > previousStates;
     135            4 :     AutomationPointEdits automationEdits;
     136            4 :     automationEdits.reserve(m_pointStates.size());
     137              : 
     138            8 :     for (const auto& [tick, point] : m_pointStates) {
     139            4 :         const auto it = curve.find(tick);
     140            4 :         if (it != curve.cend()) {
     141            2 :             previousStates.emplace(tick, it->second);
     142              :         } else {
     143            2 :             previousStates.emplace(tick, std::nullopt);
     144              :         }
     145              : 
     146            4 :         if (point) {
     147            2 :             automationEdits.push_back({ tick, AutomationPointEdit::SetPoint { *point } });
     148              :         } else {
     149            2 :             automationEdits.push_back({ tick, AutomationPointEdit::ErasePoint {} });
     150              :         }
     151              :     }
     152              : 
     153            4 :     if (m_changedRange) {
     154            4 :         m_controller->editPoints(m_key, automationEdits);
     155            4 :         widenChangedRange(m_score, m_key, automationEdits, *m_changedRange);
     156              :     }
     157              : 
     158            4 :     m_pointStates = std::move(previousStates);
     159            4 : }
        

Generated by: LCOV version 2.0-1