LCOV - code coverage report
Current view: top level - src/engraving/dom - input.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 31.0 % 184 57
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) 2021 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 "input.h"
      24              : 
      25              : #include "articulation.h"
      26              : #include "chord.h"
      27              : #include "durationtype.h"
      28              : #include "hook.h"
      29              : #include "measure.h"
      30              : #include "note.h"
      31              : #include "part.h"
      32              : #include "rest.h"
      33              : #include "score.h"
      34              : #include "segment.h"
      35              : #include "select.h"
      36              : #include "staff.h"
      37              : #include "stem.h"
      38              : #include "tuplet.h"
      39              : 
      40              : using namespace mu;
      41              : 
      42              : namespace mu::engraving {
      43              : class DrumSet;
      44              : 
      45            5 : bool InputState::isValid() const
      46              : {
      47            5 :     return m_segment != nullptr && m_track != muse::nidx;
      48              : }
      49              : 
      50              : //---------------------------------------------------------
      51              : //   drumset
      52              : //---------------------------------------------------------
      53              : 
      54            0 : Staff* InputState::staff() const
      55              : {
      56            0 :     if (!m_segment || m_track == muse::nidx) {
      57            0 :         return nullptr;
      58              :     }
      59              : 
      60            0 :     return m_segment->score()->staff(mu::engraving::track2staff(m_track));
      61              : }
      62              : 
      63            0 : staff_idx_t InputState::staffIdx() const
      64              : {
      65            0 :     return mu::engraving::track2staff(m_track);
      66              : }
      67              : 
      68           75 : Drumset* InputState::drumset() const
      69              : {
      70           75 :     if (!m_segment || m_track == muse::nidx) {
      71            0 :         return nullptr;
      72              :     }
      73              : 
      74           75 :     const Staff* staff = m_segment->score()->staff(m_track / VOICES);
      75           75 :     if (!staff) {
      76            0 :         return nullptr;
      77              :     }
      78              : 
      79           75 :     return staff->part()->instrument(m_segment->tick())->drumset();
      80              : }
      81              : 
      82              : //---------------------------------------------------------
      83              : //   staffGroup
      84              : //---------------------------------------------------------
      85              : 
      86            0 : StaffGroup InputState::staffGroup() const
      87              : {
      88            0 :     if (!m_segment || m_track == muse::nidx) {
      89            0 :         return StaffGroup::STANDARD;
      90              :     }
      91              : 
      92            0 :     Fraction tick = m_segment->tick();
      93            0 :     const Staff* staff = m_segment->score()->staff(m_track / VOICES);
      94            0 :     if (!staff) {
      95            0 :         return StaffGroup::STANDARD;
      96              :     }
      97              : 
      98            0 :     StaffGroup staffGroup = staff->staffType(tick)->group();
      99            0 :     const Instrument* instrument = staff->part()->instrument(tick);
     100              : 
     101              :     // if not tab, pitched/unpitched input depends on instrument, not staff (override StaffGroup)
     102            0 :     if (staffGroup != StaffGroup::TAB) {
     103            0 :         staffGroup = instrument->useDrumset() ? StaffGroup::PERCUSSION : StaffGroup::STANDARD;
     104              :     }
     105              : 
     106            0 :     return staffGroup;
     107              : }
     108              : 
     109              : //---------------------------------------------------------
     110              : //   tick
     111              : //---------------------------------------------------------
     112              : 
     113            0 : Fraction InputState::tick() const
     114              : {
     115            0 :     return m_segment ? m_segment->tick() : Fraction(0, 1);
     116              : }
     117              : 
     118              : //---------------------------------------------------------
     119              : //   cr
     120              : //---------------------------------------------------------
     121              : 
     122          171 : ChordRest* InputState::cr() const
     123              : {
     124              :     // _track could potentially be invalid, for instance after navigation through a frame
     125          171 :     return m_segment && m_track != muse::nidx ? toChordRest(m_segment->element(m_track)) : 0;
     126              : }
     127              : 
     128              : //---------------------------------------------------------
     129              : //   setDots
     130              : //---------------------------------------------------------
     131              : 
     132            0 : void InputState::setDots(int n)
     133              : {
     134            0 :     if (n && (!m_duration.isValid() || m_duration.isZero() || m_duration.isMeasure())) {
     135            0 :         m_duration = DurationType::V_QUARTER;
     136              :     }
     137            0 :     m_duration.setDots(n);
     138            0 : }
     139              : 
     140            8 : void InputState::setVoice(voice_idx_t v)
     141              : {
     142            8 :     const Score* score = m_segment ? m_segment->score() : nullptr;
     143              : 
     144            8 :     if (!score || v >= VOICES || v == voice() || m_track == muse::nidx) {
     145            0 :         return;
     146              :     }
     147              : 
     148            8 :     const track_idx_t newTrack = (m_track / VOICES) * VOICES + v;
     149            8 :     Segment* currSeg = segment();
     150            8 :     if (!currSeg || currSeg->cr(newTrack)) {
     151            1 :         setTrack(newTrack);
     152            1 :         return;
     153              :     }
     154              : 
     155              :     // If we haven't returned early - it means that currSeg doesn't have a valid ChordRest for the desired voice. If
     156              :     // tuplets are involved, we may need to move the input position backwards to the last valid ChordRest...
     157            7 :     Segment* candidateSeg = currSeg;
     158              : 
     159              :     // First thing to check - is currSeg part of a tuplet? If so, move candidateSeg to the start of that tuplet...
     160            7 :     const ChordRest* currCR = currSeg->cr(track());
     161            7 :     const Tuplet* currTuplet = currCR ? currCR->topTuplet() : nullptr;
     162            7 :     if (currTuplet) {
     163            5 :         candidateSeg = score->tick2segment(currTuplet->tick());
     164            5 :         IF_ASSERT_FAILED(candidateSeg) {
     165            0 :             setSegment(currSeg->measure()->first());
     166            0 :             setTrack(newTrack);
     167            0 :             return;
     168              :         }
     169              :     }
     170              : 
     171              :     // If candidateSeg still doesn't have a valid ChordRest for the desired voice, we'll now check whether it is
     172              :     // within the range of a tuplet at our desired voice. If so, move candidateSeg to the start of that tuplet...
     173            7 :     const ChordRest* nextCR = candidateSeg->nextChordRest(newTrack, /*backwards*/ true, /*stopAtMeasureBoundary*/ true);
     174            7 :     const Tuplet* nextTuplet = nextCR ? nextCR->topTuplet() : nullptr;
     175            7 :     if (!candidateSeg->cr(newTrack) && nextTuplet) {
     176            1 :         candidateSeg = score->tick2segment(nextTuplet->tick());
     177            1 :         IF_ASSERT_FAILED(candidateSeg) {
     178            0 :             setSegment(currSeg->measure()->first());
     179            0 :             setTrack(newTrack);
     180            0 :             return;
     181              :         }
     182              :     }
     183              : 
     184            7 :     if (candidateSeg != currSeg) {
     185            6 :         setSegment(candidateSeg);
     186              :     }
     187              : 
     188            7 :     setTrack(newTrack);
     189              : }
     190              : 
     191              : //---------------------------------------------------------
     192              : //   note
     193              : //---------------------------------------------------------
     194              : 
     195           11 : Note* InputState::note(EngravingItem* e)
     196              : {
     197           11 :     return e && e->isNote() ? toNote(e) : nullptr;
     198              : }
     199              : 
     200              : //---------------------------------------------------------
     201              : //   chordRest
     202              : //---------------------------------------------------------
     203              : 
     204            1 : ChordRest* InputState::chordRest(EngravingItem* e)
     205              : {
     206            1 :     if (!e) {
     207            1 :         return nullptr;
     208              :     }
     209            0 :     if (e->isChordRest()) {
     210            0 :         return toChordRest(e);
     211              :     }
     212            0 :     if (e->isNote()) {
     213            0 :         return toNote(e)->chord();
     214              :     }
     215            0 :     if (e->isStem()) {
     216            0 :         return toStem(e)->chord();
     217              :     }
     218            0 :     if (e->isHook()) {
     219            0 :         return toHook(e)->chord();
     220              :     }
     221            0 :     return nullptr;
     222              : }
     223              : 
     224              : //---------------------------------------------------------
     225              : //   update
     226              : //---------------------------------------------------------
     227              : 
     228            0 : void InputState::update(Selection& selection)
     229              : {
     230            0 :     setDuration(DurationType::V_INVALID);
     231            0 :     setRest(false);
     232            0 :     setAccidentalType(AccidentalType::NONE);
     233            0 :     setNotes({});
     234            0 :     setBeyondScore(false);
     235              : 
     236            0 :     Note* n1 = nullptr;
     237            0 :     ChordRest* cr1 = nullptr;
     238            0 :     bool differentAccidentals = false;
     239            0 :     bool differentDurations = false;
     240            0 :     bool chordsAndRests = false;
     241              : 
     242            0 :     std::set<SymId> articulationSymbolIds;
     243            0 :     for (EngravingItem* e : selection.elements()) {
     244            0 :         if (Note* n = note(e)) {
     245            0 :             if (n1) {
     246            0 :                 if (n->accidentalType() != n1->accidentalType()) {
     247            0 :                     setAccidentalType(AccidentalType::NONE);
     248            0 :                     differentAccidentals = true;
     249              :                 }
     250              : 
     251            0 :                 std::set<SymId> articulationsIds;
     252            0 :                 for (Articulation* artic: n->chord()->articulations()) {
     253            0 :                     articulationsIds.insert(artic->symId());
     254              :                 }
     255              : 
     256            0 :                 articulationsIds = mu::engraving::splitArticulations(articulationsIds);
     257            0 :                 articulationsIds = mu::engraving::flipArticulations(articulationsIds, PlacementV::ABOVE);
     258            0 :                 for (const SymId& articulationSymbolId: articulationsIds) {
     259            0 :                     if (std::find(articulationSymbolIds.begin(), articulationSymbolIds.end(),
     260            0 :                                   articulationSymbolId) == articulationSymbolIds.end()) {
     261            0 :                         articulationSymbolIds.erase(articulationSymbolId);
     262              :                     }
     263              :                 }
     264            0 :             } else {
     265            0 :                 setAccidentalType(n->accidentalType());
     266              : 
     267            0 :                 std::set<SymId> articulationsIds;
     268            0 :                 for (Articulation* artic: n->chord()->articulations()) {
     269            0 :                     if (!artic->isOrnament()) {
     270            0 :                         articulationsIds.insert(artic->symId());
     271              :                     }
     272              :                 }
     273            0 :                 articulationSymbolIds = mu::engraving::flipArticulations(articulationsIds, PlacementV::ABOVE);
     274              : 
     275            0 :                 n1 = n;
     276            0 :             }
     277              :         }
     278              : 
     279            0 :         if (ChordRest* cr = chordRest(e)) {
     280            0 :             if (cr1) {
     281            0 :                 if (cr->durationType() != cr1->durationType()) {
     282            0 :                     setDuration(DurationType::V_INVALID);
     283            0 :                     differentDurations = true;
     284              :                 }
     285            0 :                 if ((cr->isRest() && !cr1->isRest()) || (!cr->isRest() && cr1->isRest())) {
     286            0 :                     setRest(false);
     287            0 :                     chordsAndRests = true;
     288              :                 }
     289              :             } else {
     290            0 :                 setDuration(cr->durationType());
     291            0 :                 setRest(cr->isRest());
     292            0 :                 cr1 = cr;
     293              :             }
     294              :         }
     295              : 
     296            0 :         if (differentAccidentals && differentDurations && chordsAndRests) {
     297            0 :             break;
     298              :         }
     299              :     }
     300              : 
     301            0 :     setArticulationIds(joinArticulations(articulationSymbolIds));
     302              : 
     303            0 :     EngravingItem* e = selection.element();
     304            0 :     if (!e) {
     305            0 :         if (!selection.isNone()) {
     306            0 :             setTrack(selection.activeTrack());
     307            0 :             setSegment(selection.startSegment());
     308              :         }
     309            0 :         return;
     310              :     }
     311              : 
     312            0 :     ChordRest* cr = chordRest(e);
     313            0 :     Note* n = note(e);
     314            0 :     if (!n && cr && cr->isChord()) {
     315            0 :         n = toChord(cr)->upNote();
     316              :     }
     317              : 
     318            0 :     if (cr) {
     319            0 :         setTrack(cr->track());
     320            0 :         setNoteType(n ? n->noteType() : NoteType::NORMAL);
     321            0 :         setBeamMode(cr->beamMode());
     322              :     }
     323              : 
     324            0 :     setDrumNote(-1);
     325            0 :     if (n) {
     326            0 :         const Instrument* instr = n->part()->instrument(n->tick());
     327            0 :         if (instr->useDrumset()) {
     328            0 :             setDrumNote(n->pitch());
     329              :         }
     330              :     }
     331            0 : }
     332              : 
     333              : //---------------------------------------------------------
     334              : //   moveInputPos
     335              : //---------------------------------------------------------
     336              : 
     337            0 : void InputState::moveInputPos(EngravingItem* e)
     338              : {
     339            0 :     if (e == 0) {
     340            0 :         return;
     341              :     }
     342              : 
     343              :     Segment* s;
     344            0 :     if (e->isChordRest()) {
     345            0 :         s = toChordRest(e)->segment();
     346              :     } else {
     347            0 :         s = toSegment(e);
     348              :     }
     349              : 
     350            0 :     if (s->isSegment()) {
     351            0 :         if (s->measure()->isMMRest()) {
     352            0 :             Measure* m = s->measure()->mmRestFirst();
     353            0 :             s = m->findSegment(SegmentType::ChordRest, m->tick());
     354              :         }
     355            0 :         m_lastSegment = m_segment;
     356            0 :         m_segment = s;
     357              :     }
     358              : }
     359              : 
     360              : //---------------------------------------------------------
     361              : //   setSegment
     362              : //---------------------------------------------------------
     363              : 
     364         1554 : void InputState::setSegment(Segment* s)
     365              : {
     366         1554 :     if (s && s->measure()->isMMRest()) {
     367            0 :         Measure* m = s->measure()->mmRestFirst();
     368            0 :         s = m->findSegment(SegmentType::ChordRest, m->tick());
     369              :     }
     370         1554 :     m_segment = s;
     371         1554 :     m_lastSegment = s;
     372         1554 : }
     373              : 
     374              : //---------------------------------------------------------
     375              : //   nextInputPos
     376              : //---------------------------------------------------------
     377              : 
     378          141 : Segment* InputState::nextInputPos() const
     379              : {
     380          141 :     Measure* m = m_segment->measure();
     381          141 :     Segment* s = m_segment->next1(SegmentType::ChordRest);
     382          146 :     while (s) {
     383          139 :         if (s->element(m_track) || s->measure() != m) {
     384          134 :             return s;
     385              :         }
     386            5 :         s = s->next1(SegmentType::ChordRest);
     387              :     }
     388            7 :     return 0;
     389              : }
     390              : 
     391              : //---------------------------------------------------------
     392              : //   moveToNextInputPos
     393              : //   TODO: special case: note is first note of tie: goto to last note of tie
     394              : //---------------------------------------------------------
     395              : 
     396          141 : void InputState::moveToNextInputPos()
     397              : {
     398          141 :     Segment* s   = nextInputPos();
     399          141 :     m_lastSegment = m_segment;
     400          141 :     if (s) {
     401          134 :         m_segment = s;
     402          134 :         m_beyondScore = false;
     403              :     } else {
     404            7 :         m_beyondScore = true;
     405              :     }
     406          141 : }
     407              : }
        

Generated by: LCOV version 2.0-1