LCOV - code coverage report
Current view: top level - src/engraving/editing - noteinput.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 24.8 % 1008 250
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
       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 "noteinput.h"
      24              : 
      25              : #include <algorithm>
      26              : #include <iterator>
      27              : #include <tuple>
      28              : 
      29              : #include "translation.h"
      30              : 
      31              : #include "editmeasures.h"
      32              : #include "editnote.h"
      33              : #include "inserttime.h"
      34              : #include "mscoreview.h"
      35              : #include "regroup.h"
      36              : #include "transaction/transaction.h"
      37              : #include "transpose.h"
      38              : 
      39              : #include "../infrastructure/messagebox.h"
      40              : 
      41              : #include "../dom/accidental.h"
      42              : #include "../dom/chord.h"
      43              : #include "../dom/chordline.h"
      44              : #include "../dom/drumset.h"
      45              : #include "../dom/durationtype.h"
      46              : #include "../dom/excerpt.h"
      47              : #include "../dom/factory.h"
      48              : #include "../dom/input.h"
      49              : #include "../dom/masterscore.h"
      50              : #include "../dom/measure.h"
      51              : #include "../dom/measurerepeat.h"
      52              : #include "../dom/note.h"
      53              : #include "../dom/noteval.h"
      54              : #include "../dom/part.h"
      55              : #include "../dom/range.h"
      56              : #include "../dom/rest.h"
      57              : #include "../dom/score.h"
      58              : #include "../dom/segment.h"
      59              : #include "../dom/select.h"
      60              : #include "../dom/slur.h"
      61              : #include "../dom/staff.h"
      62              : #include "../dom/stafftype.h"
      63              : #include "../dom/stringdata.h"
      64              : #include "../dom/tie.h"
      65              : #include "../dom/tremolotwochord.h"
      66              : #include "../dom/tuplet.h"
      67              : #include "../dom/utils.h"
      68              : 
      69              : #include "navigation.h"
      70              : 
      71              : #include "log.h"
      72              : 
      73              : using namespace muse;
      74              : using namespace mu;
      75              : using namespace mu::engraving;
      76              : 
      77              : //---------------------------------------------------------
      78              : //   resolveNoteInputParams
      79              : //---------------------------------------------------------
      80              : 
      81            0 : bool NoteInput::resolveNoteInputParams(const Score* score, int note, bool addFlag, NoteInputParams& out)
      82              : {
      83            0 :     const InputState& is = score->inputState();
      84              : 
      85              :     //! NOTE: Drumset params should be defined explicitly (see NotationViewInputController::tryPercussionShortcut)
      86            0 :     if (!is.isValid() || is.drumset()) {
      87            0 :         return false;
      88              :     }
      89              : 
      90            0 :     int octave = 4;
      91              : 
      92              :     static const int tab[] = { 0, 2, 4, 5, 7, 9, 11 };
      93              : 
      94              :     // if adding notes, add above the upNote of the current chord
      95            0 :     EngravingItem* el = score->selection().element();
      96            0 :     if (addFlag && el && el->isNote()) {
      97            0 :         Chord* chord = toNote(el)->chord();
      98            0 :         Note* n      = chord->upNote();
      99            0 :         int tpc = n->tpc();
     100            0 :         octave = (n->epitch() - int(tpc2alter(tpc))) / PITCH_DELTA_OCTAVE;
     101            0 :         if (note <= tpc2step(tpc)) {
     102            0 :             octave++;
     103              :         }
     104              :     } else {
     105            0 :         int curPitch = 60;
     106            0 :         if (is.segment()) {
     107            0 :             const Staff* staff = score->staff(is.track() / VOICES);
     108            0 :             Segment* seg = is.segment()->prev1(SegmentType::ChordRest | SegmentType::Clef | SegmentType::HeaderClef);
     109            0 :             while (seg) {
     110            0 :                 if (seg->isChordRestType()) {
     111            0 :                     EngravingItem* p = seg->element(is.track());
     112            0 :                     if (p && p->isChord()) {
     113            0 :                         Note* n = toChord(p)->downNote();
     114              :                         // forget any accidental and/or adjustment due to key signature
     115            0 :                         curPitch = n->epitch() - static_cast<int>(tpc2alter(n->tpc()));
     116            0 :                         break;
     117              :                     }
     118            0 :                 } else if (seg->isClefType() || seg->isHeaderClefType()) {
     119            0 :                     EngravingItem* p = seg->element(trackZeroVoice(is.track()));                  // clef on voice 1
     120            0 :                     if (p && p->isClef()) {
     121            0 :                         Clef* clef = toClef(p);
     122              :                         // check if it's an actual change or just a courtesy
     123            0 :                         ClefType ctb = staff->clef(clef->tick() - Fraction::eps());
     124            0 :                         if (ctb != clef->clefType() || clef->tick().isZero()) {
     125            0 :                             curPitch = line2pitch(4, clef->clefType(), Key::C);                     // C 72 for treble clef
     126            0 :                             break;
     127              :                         }
     128              :                     }
     129              :                 }
     130            0 :                 seg = seg->prev1MM(SegmentType::ChordRest | SegmentType::Clef | SegmentType::HeaderClef);
     131              :             }
     132            0 :             octave = curPitch / 12;
     133              :         }
     134              : 
     135            0 :         int delta = octave * 12 + tab[note] - curPitch;
     136            0 :         if (delta > 6) {
     137            0 :             --octave;
     138            0 :         } else if (delta < -6) {
     139            0 :             ++octave;
     140              :         }
     141              :     }
     142              : 
     143            0 :     out.step = octave * 7 + note;
     144            0 :     return true;
     145              : }
     146              : 
     147              : //---------------------------------------------------------
     148              : //   noteVal
     149              : //---------------------------------------------------------
     150              : 
     151            0 : NoteVal NoteInput::noteVal(const Score* score, int pitch, staff_idx_t staffIdx, bool allowTransposition)
     152              : {
     153            0 :     NoteVal nval(pitch);
     154              : 
     155            0 :     const Staff* st = score->staff(staffIdx);
     156            0 :     if (!st) {
     157            0 :         return nval;
     158              :     }
     159              : 
     160            0 :     bool concertPitch = score->style().styleB(Sid::concertPitch);
     161            0 :     Interval v = st->part()->instrument(score->inputState().tick())->transpose();
     162              : 
     163              :     // If an accidental is set in the input state, use it as a hint for the pitch spelling
     164            0 :     if (AccidentalType at = score->inputState().accidentalType(); at == AccidentalType::FLAT || at == AccidentalType::SHARP) {
     165            0 :         Prefer prefer = at == AccidentalType::SHARP ? Prefer::SHARPS : Prefer::FLATS;
     166            0 :         if (concertPitch || v.isZero()) {
     167              :             // Note: using Key::C always and ignoring actual key signature. Otherwise, flat mode would still use sharps
     168              :             // sometimes if they're in the key, and vice versa, which seems contrary to the intent of the hint.
     169            0 :             nval.tpc1 = pitch2tpc(nval.pitch, Key::C, prefer);
     170            0 :             Interval vFlipped = v;
     171            0 :             vFlipped.flip();
     172            0 :             nval.tpc2 = Transpose::transposeTpc(nval.tpc1, vFlipped, true);
     173              :         } else {
     174              :             // Spell the transposed pitch first, then convert to concert pitch
     175            0 :             int writtenPitch = nval.pitch;
     176            0 :             if (!allowTransposition) {
     177            0 :                 writtenPitch -= v.chromatic;
     178              :             }
     179            0 :             nval.tpc2 = pitch2tpc(writtenPitch, Key::C, prefer);
     180            0 :             nval.tpc1 = Transpose::transposeTpc(nval.tpc2, v, true);
     181              :         }
     182              :     }
     183              : 
     184              :     // if transposing, interpret MIDI pitch as representing desired written pitch
     185              :     // set pitch based on corresponding sounding pitch
     186            0 :     if (!concertPitch && allowTransposition) {
     187            0 :         nval.pitch += v.chromatic;
     188              :     }
     189              : 
     190            0 :     return nval;
     191              : }
     192              : 
     193              : //---------------------------------------------------------
     194              : //   noteValForPosition
     195              : //---------------------------------------------------------
     196              : 
     197           60 : NoteVal NoteInput::noteValForPosition(const Score* score, Position pos, AccidentalType at, bool& error)
     198              : {
     199           60 :     const InputState& is = score->inputState();
     200              : 
     201           60 :     error           = false;
     202           60 :     const Segment* s = pos.segment;
     203           60 :     int line        = pos.line;
     204           60 :     Fraction tick   = s->tick();
     205           60 :     staff_idx_t staffIdx = pos.staffIdx;
     206           60 :     const Staff* st = score->staff(staffIdx);
     207           60 :     ClefType clef   = st->clef(tick);
     208           60 :     const Instrument* instr = st->part()->instrument(s->tick());
     209           60 :     NoteVal nval;
     210           60 :     const StringData* stringData = nullptr;
     211              : 
     212              :     // pitched/unpitched note entry depends on instrument (override StaffGroup)
     213           60 :     StaffGroup staffGroup = st->staffType(tick)->group();
     214           60 :     if (staffGroup != StaffGroup::TAB) {
     215           60 :         staffGroup = instr->useDrumset() ? StaffGroup::PERCUSSION : StaffGroup::STANDARD;
     216              :     }
     217              : 
     218           60 :     if (staffGroup != StaffGroup::PERCUSSION) {
     219           60 :         stringData = st->part()->stringData(s->tick(), st->idx());
     220              :     }
     221              : 
     222           60 :     switch (staffGroup) {
     223            0 :     case StaffGroup::PERCUSSION: {
     224            0 :         if (is.rest()) {
     225            0 :             break;
     226              :         }
     227            0 :         const Drumset* ds = instr->drumset();
     228            0 :         nval.pitch = is.drumNote();
     229            0 :         if (!ds->isValid(nval.pitch) || ds->line(nval.pitch) != line) {
     230              :             // Drum note from input state is not valid - fall back to the first valid pitch for this line...
     231            0 :             const int defaultPitch = ds->defaultPitchForLine(line);
     232            0 :             if (ds->isValid(defaultPitch)) {
     233            0 :                 nval.pitch = defaultPitch;
     234              :             }
     235              :         }
     236            0 :         if (nval.pitch < 0) {
     237            0 :             error = true;
     238            0 :             return nval;
     239              :         }
     240            0 :         nval.headGroup = ds->noteHead(nval.pitch);
     241            0 :         if (nval.headGroup == NoteHeadGroup::HEAD_INVALID) {
     242            0 :             error = true;
     243            0 :             return nval;
     244              :         }
     245            0 :         break;
     246              :     }
     247            0 :     case StaffGroup::TAB: {
     248            0 :         if (is.rest()) {
     249            0 :             return nval;
     250              :         }
     251            0 :         line = st->staffType(tick)->visualStringToPhys(line);
     252            0 :         if (line < 0 || line >= static_cast<int>(stringData->strings())) {
     253            0 :             error = true;
     254            0 :             return nval;
     255              :         }
     256              :         // build a default NoteVal for that string
     257            0 :         nval.string = line;
     258            0 :         if (pos.fret != INVALID_FRET_INDEX) {                  // if a fret is given, use it
     259            0 :             nval.fret = pos.fret;
     260              :         } else {                                      // if no fret, use 0 as default
     261            0 :             nval.fret = 0;
     262              :         }
     263              :         // reduce within fret limit
     264            0 :         if (nval.fret > stringData->frets()) {
     265            0 :             nval.fret = stringData->frets();
     266              :         }
     267              :         // for open strings, only accepts fret 0 (strings in StringData are from bottom to top)
     268            0 :         size_t strgDataIdx = stringData->strings() - line - 1;
     269            0 :         if (nval.fret > 0 && stringData->stringList().at(strgDataIdx).open) {
     270            0 :             nval.fret = 0;
     271              :         }
     272            0 :         nval.pitch = stringData->getPitch(line, nval.fret, st, pos.segment->tick());
     273            0 :         break;
     274              :     }
     275              : 
     276           60 :     case StaffGroup::STANDARD: {
     277              :         AccidentalVal acci
     278           60 :             = (at == AccidentalType::NONE ? s->measure()->findAccidental(s, staffIdx, line, error) : Accidental::subtype2value(at));
     279           60 :         if (error) {
     280           17 :             return nval;
     281              :         }
     282           43 :         int step           = absStep(line, clef);
     283           43 :         int octave         = step / 7;
     284           43 :         nval.pitch         = step2pitch(step) + octave * 12 + int(acci);
     285           43 :         if (score->style().styleB(Sid::concertPitch)) {
     286            0 :             nval.tpc1 = step2tpc(step % 7, acci);
     287              :         } else {
     288           43 :             nval.pitch += instr->transpose().chromatic;
     289           43 :             nval.tpc2 = step2tpc(step % 7, acci);
     290           43 :             Interval v = st->transpose(tick);
     291           43 :             if (v.isZero()) {
     292           42 :                 nval.tpc1 = nval.tpc2;
     293              :             } else {
     294            1 :                 nval.tpc1 = Transpose::transposeTpc(nval.tpc2, v, true);
     295              :             }
     296              :         }
     297           43 :         stringData->convertPitch(nval.pitch, st, pos.segment->tick(), &nval.string, &nval.fret);
     298              :     }
     299           43 :     break;
     300              :     }
     301           43 :     return nval;
     302              : }
     303              : 
     304              : //---------------------------------------------------------
     305              : //   addNote from NoteVal
     306              : //---------------------------------------------------------
     307              : 
     308           36 : Note* NoteInput::addNote(Transaction&, Score* score, Chord* chord, const NoteVal& noteVal, bool forceAccidental,
     309              :                          const std::set<SymId>& articulationIds, InputState* externalInputState)
     310              : {
     311           36 :     InputState& is = externalInputState ? (*externalInputState) : score->inputState();
     312              : 
     313           36 :     Note* note = Factory::createNote(chord);
     314           36 :     note->setTrack(chord->track());
     315           36 :     note->setNval(noteVal);
     316           36 :     score->undoAddElement(note);
     317           36 :     if (forceAccidental) {
     318            0 :         int tpc = score->style().styleB(Sid::concertPitch) ? noteVal.tpc1 : noteVal.tpc2;
     319            0 :         AccidentalVal alter = tpc2alter(tpc);
     320            0 :         AccidentalType at = Accidental::value2subtype(alter);
     321            0 :         Accidental* a = Factory::createAccidental(note);
     322            0 :         a->setAccidentalType(at);
     323            0 :         a->setRole(AccidentalRole::USER);
     324            0 :         score->undoAddElement(a);
     325              :     }
     326              : 
     327           36 :     if (!articulationIds.empty()) {
     328            0 :         chord->updateArticulations(articulationIds);
     329              :     }
     330              : 
     331           36 :     score->setPlayNote(true);
     332           36 :     score->setPlayChord(true);
     333              : 
     334           36 :     if (externalInputState) {
     335            0 :         is.setTrack(note->track());
     336            0 :         is.setLastSegment(is.segment());
     337            0 :         is.setSegment(note->chord()->segment());
     338              :     } else {
     339           36 :         score->select(note, SelectType::SINGLE, 0);
     340              :     }
     341              : 
     342           36 :     if (!chord->staff()->isTabStaff(chord->tick())) {
     343           36 :         NoteEntryMethod entryMethod = is.noteEntryMethod();
     344           36 :         if (entryMethod != NoteEntryMethod::REPITCH && entryMethod != NoteEntryMethod::REALTIME_AUTO
     345           36 :             && entryMethod != NoteEntryMethod::REALTIME_MANUAL) {
     346           36 :             is.moveToNextInputPos();
     347              :         }
     348              :     }
     349           36 :     return note;
     350              : }
     351              : 
     352            0 : Note* NoteInput::addNoteToTiedChord(Transaction& tx, Score* score, Chord* chord, const NoteVal& noteVal, bool forceAccidental,
     353              :                                     const std::set<SymId>& articulationIds)
     354              : {
     355            0 :     IF_ASSERT_FAILED(!chord->notes().empty()) {
     356            0 :         return nullptr;
     357              :     };
     358            0 :     Note* referenceNote = chord->notes().at(0);
     359              : 
     360              :     while (true) {
     361              :         // don't add note if it is already part of tied notes previously
     362            0 :         if (referenceNote->chord()->findNote(noteVal.pitch)) {
     363            0 :             return nullptr;
     364              :         }
     365            0 :         if (!referenceNote->tieBack() || referenceNote->incomingPartialTie()) {
     366            0 :             break;
     367              :         }
     368            0 :         referenceNote = referenceNote->tieBack()->startNote();
     369              :     }
     370              : 
     371            0 :     Tie* tie = nullptr;
     372            0 :     Note* newNote = nullptr;
     373              : 
     374            0 :     while (referenceNote->tieFor()) {
     375            0 :         chord = referenceNote->chord();
     376            0 :         newNote = addNote(tx, score, chord, noteVal, forceAccidental, articulationIds);
     377            0 :         if (!newNote) {
     378            0 :             return nullptr;
     379              :         }
     380            0 :         if (tie) {
     381            0 :             tie->setEndNote(newNote);
     382            0 :             tie->setTick2(newNote->tick());
     383            0 :             newNote->setTieBack(tie);
     384            0 :             score->undoAddElement(tie);
     385              :         }
     386              : 
     387            0 :         tie = Factory::createTie(newNote);
     388            0 :         tie->setStartNote(newNote);
     389            0 :         tie->setTick(newNote->tick());
     390            0 :         tie->setTrack(newNote->track());
     391            0 :         newNote->setTieFor(tie);
     392              : 
     393            0 :         if (!referenceNote->tieFor()->endNote()) {
     394            0 :             break;
     395              :         }
     396              : 
     397            0 :         referenceNote = referenceNote->tieFor()->endNote();
     398              :     }
     399              : 
     400            0 :     chord = referenceNote->chord();
     401            0 :     newNote = addNote(tx, score, chord, noteVal, forceAccidental, articulationIds);
     402              : 
     403            0 :     if (!newNote) {
     404            0 :         return nullptr;
     405              :     }
     406              : 
     407            0 :     if (tie) {
     408            0 :         tie->setEndNote(newNote);
     409            0 :         tie->setTick2(newNote->tick());
     410            0 :         newNote->setTieBack(tie);
     411            0 :         score->undoAddElement(tie);
     412              :     }
     413              : 
     414            0 :     score->connectTies();
     415              : 
     416            0 :     return newNote;
     417              : }
     418              : 
     419              : //---------------------------------------------------------
     420              : //   addPitch
     421              : //---------------------------------------------------------
     422              : 
     423           80 : Note* NoteInput::addPitch(Transaction& tx, Score* score, NoteVal& nval, bool addFlag, InputState* externalInputState)
     424              : {
     425           80 :     InputState& is = externalInputState ? (*externalInputState) : score->inputState();
     426              : 
     427           80 :     if (addFlag) {
     428           10 :         const Segment* lastSegment = is.lastSegment();
     429           10 :         IF_ASSERT_FAILED(lastSegment) {
     430            0 :             return nullptr;
     431              :         }
     432           10 :         ChordRest* c = toChordRest(lastSegment->element(is.track()));
     433           10 :         if (!c || !c->isChord()) {
     434            0 :             LOGD("NoteInput::addPitch: cr %s", c ? c->typeName() : "zero");
     435            0 :             return nullptr;
     436              :         }
     437           10 :         return addPitchToChord(tx, score, nval, toChord(c), externalInputState);
     438              :     }
     439              : 
     440           70 :     if (is.beyondScore()) {
     441            0 :         score->appendMeasures(1);
     442            0 :         is.moveToNextInputPos();
     443              :     }
     444              : 
     445           70 :     score->expandVoice(is.segment(), is.track());
     446              : 
     447              :     // insert note
     448           70 :     DirectionV stemDirection = DirectionV::AUTO;
     449           70 :     track_idx_t track = is.track();
     450           70 :     if (is.drumset()) {
     451            0 :         const Drumset* ds = is.drumset();
     452            0 :         if (!ds->isValid(nval.pitch)) {
     453            0 :             return nullptr;
     454              :         }
     455              : 
     456            0 :         nval.headGroup    = ds->noteHead(nval.pitch);
     457            0 :         stemDirection     = ds->stemDirection(nval.pitch);
     458            0 :         track             = ds->voice(nval.pitch) + (is.track() / VOICES) * VOICES;
     459            0 :         is.setTrack(track);
     460            0 :         score->expandVoice(is.segment(), is.track());
     461              :     }
     462              : 
     463           70 :     if (!is.cr()) {
     464            0 :         ChordRest* prevCr = is.segment()->nextChordRest(is.track(), /*backwards*/ true, /*stopAtMeasureBoundary*/ true);
     465            0 :         if (prevCr && prevCr->endTick() > is.tick()) {
     466            0 :             const InputState inputStateToRestore = is; // because truncateChordRest will alter the input state
     467            0 :             truncateChordRest(tx, score, prevCr, is.tick(), /*fillWithRest*/ true);
     468            0 :             is = inputStateToRestore;
     469            0 :         }
     470              :     }
     471              : 
     472           70 :     Measure* measure = is.segment()->measure();
     473           70 :     if (measure->isMeasureRepeatGroup(track2staff(track))) {
     474            0 :         MeasureRepeat* mr = measure->measureRepeatElement(track2staff(track));
     475            0 :         score->deleteItem(mr); // resets any measures related to mr
     476              :     }
     477           70 :     Fraction duration;
     478           70 :     if (is.usingNoteEntryMethod(NoteEntryMethod::REALTIME_AUTO) || is.usingNoteEntryMethod(NoteEntryMethod::REALTIME_MANUAL)) {
     479              :         // FIXME: truncate duration at barline in real-time modes.
     480              :         //   The user might try to enter a duration that is too long to fit in the remaining space in the measure.
     481              :         //   We could split the duration at the barline and continue into the next bar, but this would create extra
     482              :         //   notes, extra ties, and extra pain. Instead, we simply truncate the duration at the barline.
     483            0 :         Fraction ticks2measureEnd = is.segment()->measure()->ticks() - is.segment()->rtick();
     484            0 :         duration = is.duration().fraction() > ticks2measureEnd ? ticks2measureEnd : is.duration().fraction();
     485              :     } else {
     486           70 :         duration = is.duration().fraction();
     487              :     }
     488           70 :     Note* note = nullptr;
     489           70 :     Note* lastTiedNote = nullptr;
     490           70 :     if (is.usingNoteEntryMethod(NoteEntryMethod::REPITCH) && is.cr()->isChord()) {
     491            0 :         Chord* chord = toChord(is.cr());
     492            0 :         std::tie(note, lastTiedNote) = repitchReplaceNote(tx, score, chord, nval);  // the add (not replace) case was handled above
     493           70 :     } else if (!is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) {
     494           70 :         Segment* seg = score->setNoteRest(
     495              :             is.segment(), track, nval, duration, stemDirection, /* forceAccidental */ false, is.articulationIds(), /* rhythmic */ false,
     496              :             externalInputState);
     497           70 :         EngravingItem* chordItem = seg ? seg->element(track) : nullptr;
     498           70 :         if (chordItem && chordItem->isChord()) {
     499           70 :             note = toChord(chordItem)->upNote();
     500              :         }
     501              :     }
     502              : 
     503           70 :     if (is.slur()) {
     504              :         //
     505              :         // extend slur
     506              :         //
     507            0 :         ChordRest* e = score->searchNote(is.tick(), is.track());
     508            0 :         EngravingItem* ee = is.slur()->startElement();
     509            0 :         if (e && ee) {
     510            0 :             Fraction stick = Fraction(0, 1);
     511            0 :             if (ee->isChordRest()) {
     512            0 :                 stick = toChordRest(ee)->tick();
     513            0 :             } else if (ee->isNote()) {
     514            0 :                 stick = toNote(ee)->chord()->tick();
     515              :             }
     516            0 :             if (stick == e->tick()) {
     517            0 :                 is.slur()->setTick(stick);
     518            0 :                 is.slur()->setStartElement(e);
     519              :             } else {
     520            0 :                 is.slur()->setTick2(e->tick());
     521            0 :                 is.slur()->setEndElement(e);
     522              :             }
     523            0 :         } else {
     524            0 :             LOGD("addPitch: cannot find slur note");
     525              :         }
     526              :     }
     527           70 :     if (is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) {
     528              :         // move cursor to next note, but skip tied notes (they were already repitched above)
     529            0 :         ChordRest* next = lastTiedNote ? Navigation::nextChordRest(lastTiedNote->chord()) : Navigation::nextChordRest(is.cr());
     530            0 :         while (next && !next->isChord()) {
     531            0 :             next = Navigation::nextChordRest(next);
     532              :         }
     533            0 :         if (next) {
     534            0 :             is.moveInputPos(next->segment());
     535              :         }
     536              :     } else {
     537           70 :         NoteEntryMethod entryMethod = is.noteEntryMethod();
     538           70 :         if (entryMethod != NoteEntryMethod::REALTIME_AUTO && entryMethod != NoteEntryMethod::REALTIME_MANUAL) {
     539           70 :             is.moveToNextInputPos();
     540              :         }
     541              :     }
     542           70 :     return note;
     543              : }
     544              : 
     545           10 : Note* NoteInput::addPitchToChord(Transaction& tx, Score* score, NoteVal& nval, Chord* chord, InputState* externalInputState,
     546              :                                  bool forceAccidental)
     547              : {
     548           10 :     IF_ASSERT_FAILED(chord) {
     549            0 :         return nullptr;
     550              :     }
     551              : 
     552           10 :     InputState& is = externalInputState ? (*externalInputState) : score->inputState();
     553              : 
     554           10 :     auto isTied = [](const Chord* ch) {
     555           10 :         if (ch->notes().empty()) {
     556            0 :             return false;
     557              :         }
     558           10 :         Note* n = ch->notes().at(0);
     559           10 :         return (n->tieFor() && !n->tieFor()->isLaissezVib()) || n->tieBack();
     560              :     };
     561              : 
     562           10 :     Note* note = nullptr;
     563           10 :     if (isTied(chord)) {
     564            0 :         note = addNoteToTiedChord(tx, score, chord, nval, forceAccidental);
     565            0 :         if (!note) {
     566            0 :             note = addNote(tx, score, chord, nval, forceAccidental, /* articulationIds */ {}, externalInputState);
     567              :         }
     568              :     } else {
     569           10 :         note = addNote(tx, score, chord, nval, forceAccidental, /* articulationIds */ {}, externalInputState);
     570              :     }
     571              : 
     572           10 :     if (is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) {
     573              :         // move cursor to next note
     574            0 :         ChordRest* next = Navigation::nextChordRest(note->chord());
     575            0 :         while (next && !next->isChord()) {
     576            0 :             next = Navigation::nextChordRest(next);
     577              :         }
     578            0 :         if (next) {
     579            0 :             is.moveInputPos(next->segment());
     580              :         }
     581           10 :     } else if (is.lastSegment() == is.segment()) {
     582            0 :         NoteEntryMethod entryMethod = is.noteEntryMethod();
     583            0 :         if (entryMethod != NoteEntryMethod::REALTIME_AUTO && entryMethod != NoteEntryMethod::REALTIME_MANUAL) {
     584            0 :             is.moveToNextInputPos();
     585              :         }
     586              :     }
     587              : 
     588           10 :     return note;
     589              : }
     590              : 
     591              : //---------------------------------------------------------
     592              : //   addMidiPitch
     593              : //---------------------------------------------------------
     594              : 
     595            0 : Note* NoteInput::addMidiPitch(Transaction& tx, Score* score, int pitch, bool addFlag, bool allowTransposition)
     596              : {
     597            0 :     NoteVal nval = noteVal(score, pitch, score->inputState().staffIdx(), allowTransposition);
     598            0 :     return addPitch(tx, score, nval, addFlag);
     599              : }
     600              : 
     601              : //---------------------------------------------------------
     602              : //   addTiedMidiPitch
     603              : //---------------------------------------------------------
     604              : 
     605            0 : Note* NoteInput::addTiedMidiPitch(Transaction& tx, Score* score, int pitch, bool addFlag, Chord* prevChord, bool allowTransposition)
     606              : {
     607            0 :     Note* n = addMidiPitch(tx, score, pitch, addFlag, allowTransposition);
     608            0 :     if (prevChord) {
     609            0 :         Note* nn = prevChord->findNote(n->pitch());
     610            0 :         if (nn) {
     611            0 :             Tie* tie = Factory::createTie(nn);
     612            0 :             tie->setStartNote(nn);
     613            0 :             tie->setEndNote(n);
     614            0 :             tie->setTick(tie->startNote()->tick());
     615            0 :             tie->setTick2(tie->endNote()->tick());
     616            0 :             tie->setTrack(n->track());
     617            0 :             n->setTieBack(tie);
     618            0 :             nn->setTieFor(tie);
     619            0 :             score->undoAddElement(tie);
     620              :         }
     621              :     }
     622            0 :     return n;
     623              : }
     624              : 
     625              : //---------------------------------------------------------
     626              : //   repitchReplaceNote
     627              : //---------------------------------------------------------
     628              : 
     629            0 : std::pair<Note*, Note*> NoteInput::repitchReplaceNote(Transaction&, Score* score, Chord* chord, const NoteVal& nval,
     630              :                                                       bool forceAccidental)
     631              : {
     632            0 :     Note* note = Factory::createNote(chord);
     633            0 :     note->setTrack(chord->track());
     634            0 :     note->setNval(nval);
     635              : 
     636            0 :     Note* firstTiedNote = nullptr;
     637            0 :     Note* lastTiedNote = note;
     638            0 :     ChordLine* chordLine = nullptr;
     639            0 :     std::vector<Note*> notes = chord->notes();
     640              :     // break all ties into current chord
     641              :     // these will exist only if user explicitly moved cursor to a tied-into note
     642              :     // in ordinary use, cursor will automatically skip past these during note entry
     643            0 :     for (Note* n : notes) {
     644            0 :         if (n->tieBack()) {
     645            0 :             score->undoRemoveElement(n->tieBack());
     646              :         }
     647              :     }
     648              :     // Keep first chordline only
     649            0 :     chordLine = chord->chordLine() ? chord->chordLine()->clone() : nullptr;
     650            0 :     std::vector<EngravingItem*> chordEls = chord->el();
     651            0 :     for (EngravingItem* e : chordEls) {
     652            0 :         if (e->isChordLine()) {
     653            0 :             score->undoRemoveElement(e);
     654              :         }
     655              :     }
     656              :     // for the first note of the chord only, preserve ties by changing pitch of all forward notes
     657              :     // the tie forward itself will be added later
     658              :     // multi-note chords get reduced to single note chords anyhow since we remove the old notes below
     659            0 :     if (notes.front()->tieFor()) {
     660            0 :         Note* tn = notes.front()->tieFor()->endNote();
     661            0 :         while (tn) {
     662            0 :             Chord* tc = tn->chord();
     663            0 :             if (tc->notes().size() != 1) {
     664            0 :                 std::vector<Note*> notesToRemove;
     665            0 :                 for (Note* n : tc->notes()) {
     666            0 :                     if (n != tn) {
     667            0 :                         notesToRemove.push_back(n);
     668              :                     }
     669              :                 }
     670            0 :                 for (Note* n : notesToRemove) {
     671            0 :                     score->undoRemoveElement(n);
     672              :                 }
     673            0 :                 assert(tc->notes().size() == 1 && tc->notes().front() == tn);
     674            0 :             }
     675            0 :             if (!firstTiedNote) {
     676            0 :                 firstTiedNote = tn;
     677              :             }
     678            0 :             lastTiedNote = tn;
     679            0 :             EditNote::undoChangePitch(score, tn, note->pitch(), note->tpc1(), note->tpc2());
     680            0 :             if (tn->tieFor()) {
     681            0 :                 tn = tn->tieFor()->endNote();
     682              :             } else {
     683            0 :                 break;
     684              :             }
     685              :         }
     686              :     }
     687              :     // remove all notes from chord
     688              :     // the new note will be added below
     689            0 :     while (!chord->notes().empty()) {
     690            0 :         score->undoRemoveElement(chord->notes().front());
     691              :     }
     692            0 :     notes.clear();  // get rid of vector full of dangling pointers!
     693              : 
     694              :     // add new note to chord
     695            0 :     score->undoAddElement(note);
     696              : 
     697            0 :     if (chordLine) {
     698            0 :         chordLine->setNote(note);
     699            0 :         score->undoAddElement(chordLine);
     700              :     }
     701              : 
     702            0 :     if (forceAccidental) {
     703            0 :         int tpc = score->style().styleB(Sid::concertPitch) ? nval.tpc1 : nval.tpc2;
     704            0 :         AccidentalVal alter = tpc2alter(tpc);
     705            0 :         AccidentalType at = Accidental::value2subtype(alter);
     706            0 :         Accidental* a = Factory::createAccidental(note);
     707            0 :         a->setAccidentalType(at);
     708            0 :         a->setRole(AccidentalRole::USER);
     709            0 :         score->undoAddElement(a);
     710              :     }
     711            0 :     score->setPlayNote(true);
     712              :     // recreate tie forward if there is a note to tie to
     713              :     // one-sided ties will not be recreated
     714            0 :     if (firstTiedNote) {
     715            0 :         Tie* tie = Factory::createTie(note);
     716            0 :         tie->setStartNote(note);
     717            0 :         tie->setEndNote(firstTiedNote);
     718            0 :         tie->setTick(tie->startNote()->tick());
     719            0 :         tie->setTick2(tie->endNote()->tick());
     720            0 :         tie->setTrack(note->track());
     721            0 :         score->undoAddElement(tie);
     722              :     }
     723            0 :     score->select(lastTiedNote);
     724              : 
     725            0 :     return { note, lastTiedNote };
     726            0 : }
     727              : 
     728              : //---------------------------------------------------------
     729              : //   putNote
     730              : //    mouse click in state NoteType::ENTRY
     731              : //---------------------------------------------------------
     732              : 
     733            0 : Ret NoteInput::putNote(Transaction& tx, Score* score, const PointF& pos, bool replace, bool insert)
     734              : {
     735            0 :     Position p;
     736            0 :     if (!score->getPosition(&p, pos, score->inputState().voice())) {
     737            0 :         LOGD("cannot put note here, get position failed");
     738            0 :         return make_ret(Ret::Code::UnknownError);
     739              :     }
     740              : 
     741            0 :     Score* posScore = p.segment->score();
     742              :     // it is not safe to call repitchNote() if p is on a TAB staff
     743            0 :     bool isTablature = posScore->staff(p.staffIdx)->isTabStaff(p.segment->tick());
     744              : 
     745              :     //  calculate actual clicked line from staffType offset and stepOffset
     746            0 :     Staff* ss = posScore->staff(p.staffIdx);
     747            0 :     int stepOffset = ss->staffType(p.segment->tick())->stepOffset();
     748            0 :     double stYOffset = ss->staffType(p.segment->tick())->yoffset().val();
     749            0 :     double lineDist = ss->staffType(p.segment->tick())->lineDistance().val();
     750            0 :     p.line -= stepOffset + 2 * stYOffset / lineDist;
     751              : 
     752            0 :     if (posScore->inputState().usingNoteEntryMethod(NoteEntryMethod::REPITCH) && !isTablature) {
     753            0 :         return repitchNote(tx, posScore, p, replace);
     754              :     } else {
     755            0 :         if (insert || posScore->inputState().usingNoteEntryMethod(NoteEntryMethod::TIMEWISE)) {
     756            0 :             return insertChordByInsertingTime(tx, posScore, p);
     757              :         } else {
     758            0 :             return putNote(tx, posScore, p, replace);
     759              :         }
     760              :     }
     761              : }
     762              : 
     763           27 : Ret NoteInput::putNote(Transaction& tx, Score* score, const Position& p, bool replace)
     764              : {
     765           27 :     InputState& is = score->inputState();
     766              : 
     767           27 :     Staff* st   = score->staff(p.staffIdx);
     768           27 :     Segment* s  = p.segment;
     769              : 
     770           27 :     is.setTrack(p.staffIdx * VOICES + is.voice());
     771           27 :     is.setSegment(s);
     772              : 
     773           27 :     if (p.beyondScore) {
     774            0 :         score->appendMeasures(1);
     775            0 :         is.moveToNextInputPos();
     776              :     }
     777              : 
     778           27 :     if (mu::engraving::Excerpt* excerpt = score->excerpt()) {
     779            0 :         const TracksMap& tracks = excerpt->tracksMapping();
     780              : 
     781            0 :         if (!tracks.empty() && muse::key(tracks, is.track(), muse::nidx) == muse::nidx) {
     782            0 :             return make_ret(Ret::Code::UnknownError);
     783              :         }
     784              :     }
     785              : 
     786           27 :     DirectionV stemDirection = DirectionV::AUTO;
     787           27 :     bool error = false;
     788           27 :     NoteVal nval = noteValForPosition(score, p, is.accidentalType(), error);
     789           27 :     if (error) {
     790            2 :         return make_ret(Ret::Code::UnknownError);
     791              :     }
     792              : 
     793              :     // warn and delete MeasureRepeat if necessary
     794           25 :     Measure* m = is.segment()->measure();
     795           25 :     staff_idx_t staffIdx = track2staff(is.track());
     796           25 :     if (m->isMeasureRepeatGroup(staffIdx)) {
     797            0 :         auto b = MessageBox(score->iocContext()).warning(muse::trc("engraving", "Note input will remove measure repeat"),
     798            0 :                                                          muse::trc("engraving", "This measure contains a measure repeat."
     799              :                                                                                 " If you enter notes here, it will be deleted."
     800              :                                                                                 " Do you want to continue?"));
     801            0 :         if (b == MessageBox::Cancel) {
     802            0 :             return make_ret(Ret::Code::Cancel);
     803              :         }
     804              : 
     805            0 :         score->deleteItem(m->measureRepeatElement(staffIdx));
     806              :     }
     807              : 
     808           25 :     const StringData* stringData = 0;
     809              : 
     810              :     // pitched/unpitched note entry depends on instrument (override StaffGroup)
     811           25 :     StaffGroup staffGroup = st->staffType(s->tick())->group();
     812           25 :     if (staffGroup != StaffGroup::TAB) {
     813           25 :         staffGroup = st->part()->instrument(s->tick())->useDrumset() ? StaffGroup::PERCUSSION : StaffGroup::STANDARD;
     814              :     }
     815              : 
     816           25 :     switch (staffGroup) {
     817            0 :     case StaffGroup::PERCUSSION: {
     818            0 :         const Drumset* ds = st->part()->instrument(s->tick())->drumset();
     819            0 :         DO_ASSERT(ds);
     820              : 
     821            0 :         if (ds) {
     822            0 :             stemDirection = ds->stemDirection(nval.pitch);
     823            0 :             is.setVoice(ds->voice(nval.pitch));
     824              :         }
     825            0 :         break;
     826              :     }
     827            0 :     case StaffGroup::TAB:
     828            0 :         stringData = st->part()->stringData(s->tick(), st->idx());
     829            0 :         is.setDrumNote(-1);
     830            0 :         break;
     831           25 :     case StaffGroup::STANDARD:
     832           25 :         is.setDrumNote(-1);
     833           25 :         break;
     834              :     }
     835              : 
     836           25 :     score->expandVoice();
     837              : 
     838              :     // If there's an overlapping ChordRest at the current input position, shorten it...
     839           25 :     if (!is.cr()) {
     840            0 :         ChordRest* prevCr = is.segment()->nextChordRest(is.track(), /*backwards*/ true, /*stopAtMeasureBoundary*/ true);
     841            0 :         if (prevCr && prevCr->endTick() > is.tick()) {
     842            0 :             const InputState inputStateToRestore = is; // because truncateChordRest will alter the input state
     843            0 :             truncateChordRest(tx, score, prevCr, is.tick(), /*fillWithRest*/ true);
     844            0 :             is = inputStateToRestore;
     845            0 :         }
     846              :     }
     847              : 
     848           25 :     ChordRest* cr = is.cr();
     849              : 
     850           25 :     auto checkTied = [&]() {
     851           25 :         if (!cr || !cr->isChord()) {
     852           25 :             return false;
     853              :         }
     854            0 :         auto ch = toChord(cr);
     855            0 :         return (!ch->notes().empty() && !ch->notes()[0]->tieBack())
     856            0 :                && (ch->notes()[0]->tieFor() && !ch->notes()[0]->tieFor()->isLaissezVib());
     857           25 :     };
     858              : 
     859           25 :     bool addToChord = false;
     860           25 :     bool shouldAddAsTied = checkTied();
     861              : 
     862           25 :     if (cr) {
     863              :         // retrieve total duration of current chord
     864           25 :         TDuration d = cr->durationType();
     865              :         // if not in replace mode AND chord duration == input duration AND not rest input
     866              :         // we need to add to current chord (otherwise, we will need to replace it or create a new one)
     867           50 :         if (!replace
     868            1 :             && ((d == is.duration()) || shouldAddAsTied)
     869            0 :             && cr->isChord()
     870           26 :             && !is.rest()) {
     871            0 :             if (st->isTabStaff(cr->tick())) {            // TAB
     872              :                 // if a note on same string already exists, update to new pitch/fret
     873            0 :                 for (Note* note : toChord(cr)->notes()) {
     874            0 :                     if (note->string() == nval.string) {                 // if string is the same
     875              :                         // if adding a new digit will keep fret number within fret limit,
     876              :                         // add a digit to existing fret number
     877            0 :                         if (stringData) {
     878            0 :                             int fret = note->fret() * 10 + nval.fret;
     879            0 :                             if (fret <= stringData->frets()) {
     880            0 :                                 nval.fret = fret;
     881            0 :                                 nval.pitch = stringData->getPitch(nval.string, nval.fret, st);
     882              :                             } else {
     883            0 :                                 LOGD("can't increase fret to %d", fret);
     884              :                             }
     885              :                         }
     886              :                         // set fret number (original or combined) in all linked notes
     887            0 :                         int tpc1 = note->tpc1default(nval.pitch);
     888            0 :                         int tpc2 = note->tpc2default(nval.pitch);
     889            0 :                         EditNote::undoChangeFretting(score, note, nval.pitch, nval.string, nval.fret, tpc1, tpc2);
     890            0 :                         score->setPlayNote(true);
     891            0 :                         return muse::make_ok();
     892              :                     }
     893              :                 }
     894              :             } else {                            // not TAB
     895              :                 // if a note with the same pitch already exists in the chord, remove it
     896            0 :                 Chord* chord = toChord(cr);
     897            0 :                 Note* note = chord->findNote(nval.pitch);
     898            0 :                 if (note) {
     899            0 :                     if (chord->notes().size() > 1) {
     900            0 :                         score->undoRemoveElement(note);
     901              :                     }
     902            0 :                     return muse::make_ok();
     903              :                 }
     904              :             }
     905            0 :             addToChord = true;                  // if no special case, add note to chord
     906              :         }
     907              :     }
     908           25 :     bool forceAccidental = false;
     909           25 :     if (is.accidentalType() != AccidentalType::NONE) {
     910            0 :         NoteVal nval2 = noteValForPosition(score, p, AccidentalType::NONE, error);
     911            0 :         forceAccidental = (nval.pitch == nval2.pitch);
     912              :     }
     913              : 
     914           50 :     Ret ret = muse::make_ok();
     915              : 
     916           25 :     if (addToChord && cr->isChord()) {
     917              :         // if adding, add!
     918            0 :         Chord* chord = toChord(cr);
     919            0 :         if (shouldAddAsTied) {
     920            0 :             Note* n = addNoteToTiedChord(tx, score, chord, nval, forceAccidental, is.articulationIds());
     921            0 :             if (!n) {
     922            0 :                 ret = make_ret(Ret::Code::UnknownError);
     923              :             }
     924              : 
     925            0 :             is.setAccidentalType(AccidentalType::NONE);
     926            0 :             return ret;
     927              :         }
     928            0 :         Note* note = addNote(tx, score, chord, nval, forceAccidental, is.articulationIds());
     929            0 :         if (!note) {
     930            0 :             ret = make_ret(Ret::Code::UnknownError);
     931              :         }
     932              : 
     933            0 :         is.setAccidentalType(AccidentalType::NONE);
     934            0 :         return ret;
     935              :     } else {
     936              :         // if not adding, replace current chord (or create a new one)
     937           25 :         if (is.rest()) {
     938            0 :             nval.pitch = -1;
     939              :         }
     940              : 
     941           50 :         Segment* seg = score->setNoteRest(is.segment(), is.track(), nval,
     942           25 :                                           is.duration().fraction(), stemDirection, forceAccidental, is.articulationIds());
     943           25 :         if (!seg) {
     944            0 :             ret = make_ret(Ret::Code::UnknownError);
     945              :         }
     946              : 
     947           25 :         is.setAccidentalType(AccidentalType::NONE);
     948              :     }
     949              : 
     950           25 :     if (cr && !st->isTabStaff(cr->tick())) {
     951           25 :         is.moveToNextInputPos();
     952              :     }
     953              : 
     954           25 :     return ret;
     955              : }
     956              : 
     957              : //---------------------------------------------------------
     958              : //   repitchNote
     959              : //---------------------------------------------------------
     960              : 
     961            0 : Ret NoteInput::repitchNote(Transaction& tx, Score* score, const Position& p, bool replace)
     962              : {
     963            0 :     InputState& is = score->inputState();
     964              : 
     965            0 :     Segment* s      = p.segment;
     966            0 :     Fraction tick   = s->tick();
     967            0 :     Staff* st       = score->staff(p.staffIdx);
     968            0 :     ClefType clef   = st->clef(tick);
     969              : 
     970            0 :     NoteVal nval;
     971            0 :     bool error = false;
     972            0 :     AccidentalType at = is.accidentalType();
     973            0 :     if (is.drumset() && is.drumNote() != -1) {
     974            0 :         nval.pitch = is.drumNote();
     975              :     } else {
     976              :         AccidentalVal acci
     977            0 :             = (at == AccidentalType::NONE ? s->measure()->findAccidental(s, p.staffIdx, p.line, error) : Accidental::subtype2value(at));
     978            0 :         if (error) {
     979            0 :             return make_ret(Ret::Code::UnknownError);
     980              :         }
     981              : 
     982            0 :         int step   = absStep(p.line, clef);
     983            0 :         int octave = step / 7;
     984            0 :         nval.pitch = step2pitch(step) + octave * 12 + int(acci);
     985              : 
     986            0 :         if (score->style().styleB(Sid::concertPitch)) {
     987            0 :             nval.tpc1 = step2tpc(step % 7, acci);
     988              :         } else {
     989            0 :             nval.pitch += st->part()->instrument(s->tick())->transpose().chromatic;
     990            0 :             nval.tpc2 = step2tpc(step % 7, acci);
     991              :         }
     992              :     }
     993              : 
     994            0 :     if (!is.segment()) {
     995            0 :         return make_ret(Ret::Code::UnknownError);
     996              :     }
     997              : 
     998              :     Chord* chord;
     999            0 :     ChordRest* cr = is.cr();
    1000            0 :     if (!cr) {
    1001            0 :         cr = is.segment()->nextChordRest(is.track());
    1002            0 :         if (!cr) {
    1003            0 :             return make_ret(Ret::Code::UnknownError);
    1004              :         }
    1005              :     }
    1006            0 :     if (cr->isRest()) {   //skip rests
    1007            0 :         ChordRest* next = Navigation::nextChordRest(cr);
    1008            0 :         while (next && !next->isChord()) {
    1009            0 :             next = Navigation::nextChordRest(next);
    1010              :         }
    1011            0 :         if (next) {
    1012            0 :             is.moveInputPos(next->segment());
    1013              :         }
    1014            0 :         return muse::make_ok();
    1015              :     } else {
    1016            0 :         chord = toChord(cr);
    1017              :     }
    1018              : 
    1019            0 :     bool forceAccidental = false;
    1020            0 :     if (is.accidentalType() != AccidentalType::NONE) {
    1021            0 :         NoteVal nval2 = noteValForPosition(score, p, AccidentalType::NONE, error);
    1022            0 :         forceAccidental = (nval.pitch == nval2.pitch);
    1023              :     }
    1024              : 
    1025              :     // Note: not sure this is ever called with replace == false, since add (not replace) case is handled already in cmdAddPitch
    1026            0 :     if (!replace) {
    1027            0 :         return addPitchToChord(tx, score, nval, chord, /* externalInputState */ nullptr, forceAccidental);
    1028              :     }
    1029              : 
    1030            0 :     auto [note, lastTiedNote] = repitchReplaceNote(tx, score, chord, nval, forceAccidental);
    1031            0 :     score->setPlayChord(true);
    1032              : 
    1033              :     // move to next Chord
    1034            0 :     ChordRest* next = Navigation::nextChordRest(lastTiedNote->chord());
    1035            0 :     while (next && !next->isChord()) {
    1036            0 :         next = Navigation::nextChordRest(next);
    1037              :     }
    1038            0 :     if (next) {
    1039            0 :         is.moveInputPos(next->segment());
    1040              :     }
    1041              : 
    1042            0 :     return muse::make_ok();
    1043              : }
    1044              : 
    1045              : //---------------------------------------------------------
    1046              : //   insertChordByInsertingTime
    1047              : //---------------------------------------------------------
    1048              : 
    1049            5 : Ret NoteInput::insertChordByInsertingTime(Transaction& tx, Score* score, const Position& pos)
    1050              : {
    1051              :     // insert by increasing measure length
    1052              :     //
    1053              :     // TODO:
    1054              :     //    - check voices
    1055              :     //    - split chord/rest
    1056              : 
    1057            5 :     EngravingItem* el = score->selection().element();
    1058            5 :     if (!el || !(el->isNote() || el->isRest())) {
    1059            0 :         return make_ret(Ret::Code::UnknownError);
    1060              :     }
    1061              : 
    1062            5 :     Segment* seg = pos.segment;
    1063            5 :     if (seg->splitsTuplet()) {
    1064            0 :         MScore::setError(MsError::CANNOT_INSERT_TUPLET);
    1065            0 :         return make_ret(Ret::Code::UnknownError);
    1066              :     }
    1067              : 
    1068              :     // remove all two-note tremolos that end on this tick
    1069           37 :     for (EngravingItem* e : seg->elist()) {
    1070           32 :         if (!e || !e->isChord()) {
    1071           24 :             continue;
    1072              :         }
    1073            8 :         Chord* c = toChord(e);
    1074            8 :         TremoloTwoChord* t = c->tremoloTwoChord();
    1075            8 :         if (t && t->chord2() == c) {
    1076              :             // we have to remove this tremolo because we are adding time in the middle of it
    1077              :             // (if c is chord1 then we're inserting before the trem so it's fine)
    1078            0 :             score->undoRemoveElement(t);
    1079              :         }
    1080              :     }
    1081              : 
    1082            5 :     const TDuration duration = score->inputState().duration();
    1083            5 :     const Fraction fraction  = duration.fraction();
    1084            5 :     const Fraction len       = fraction;
    1085            5 :     Fraction tick            = seg->tick();
    1086            5 :     Measure* measure         = seg->measure()->isMMRest() ? seg->measure()->mmRestFirst() : seg->measure();
    1087            5 :     const Fraction targetMeasureLen = measure->ticks() + fraction;
    1088              : 
    1089              :     // Shift spanners, enlarge the measure.
    1090              :     // The approach is similar to that in Measure::adjustToLen() but does
    1091              :     // insert time to the middle of the measure rather than to the end.
    1092            5 :     score->undoInsertTime(tick, len);
    1093              : 
    1094           10 :     for (Score* lscore : score->scoreList()) {
    1095            5 :         tx.push(new InsertTime(lscore, tick, len));
    1096            5 :         Measure* m = lscore->tick2measure(tick);
    1097            5 :         tx.push(new ChangeMeasureLen(m, targetMeasureLen));
    1098            5 :         Segment* scoreSeg = m->tick2segment(tick);
    1099              :         // tuplets store their tick: move those from the insertion on with their segments
    1100            5 :         std::set<Tuplet*> tuplets;
    1101           36 :         for (Segment* s = scoreSeg; s; s = s->next()) {
    1102           31 :             if (s->isChordRestType()) {
    1103          227 :                 for (EngravingItem* e : s->elist()) {
    1104          224 :                     for (Tuplet* t = e ? toChordRest(e)->tuplet() : nullptr; t && t->tick() >= tick; t = t->tuplet()) {
    1105              :                         // shared tuplets follow their origin
    1106           28 :                         if (t->originItems().empty()) {
    1107           25 :                             tuplets.insert(t);
    1108              :                         }
    1109              :                     }
    1110              :                 }
    1111              :             }
    1112           31 :             s->undoChangeProperty(Pid::TICK, s->rtick() + len);
    1113              :         }
    1114           12 :         for (Tuplet* t : tuplets) {
    1115            7 :             t->undoChangeProperty(Pid::TICK, t->tick() + len);
    1116              :         }
    1117           10 :     }
    1118              : 
    1119              :     // Fill the inserted time with rests.
    1120              :     // This is better to be done in master score to cover all staves.
    1121            5 :     MasterScore* ms = score->masterScore();
    1122            5 :     Measure* msMeasure = ms->tick2measure(tick);
    1123            5 :     const size_t msTracks = ms->ntracks();
    1124              : 
    1125            5 :     Segment* firstSeg = msMeasure->first(SegmentType::ChordRest);
    1126           37 :     for (track_idx_t track = 0; track < msTracks; ++track) {
    1127           32 :         EngravingItem* maybeRest = firstSeg->element(track);
    1128           32 :         bool measureIsFull = false;
    1129              : 
    1130              :         // I. Convert any measure rests into normal (non-measure) rest(s) of equivalent duration
    1131           32 :         if (maybeRest && maybeRest->isRest() && toRest(maybeRest)->durationType().isMeasure()) {
    1132            0 :             ms->undoRemoveElement(maybeRest);
    1133            0 :             Rest* measureRest = toRest(maybeRest);
    1134              :             // If measure rest is situated at measure start we will fill
    1135              :             // the whole measure with rests.
    1136            0 :             measureIsFull = measureRest->rtick().isZero();
    1137            0 :             const Fraction fillLen = measureIsFull ? targetMeasureLen : measureRest->ticks();
    1138            0 :             ms->setRest(measureRest->tick(), track, fillLen, /* useDots */ false, /* tuplet */ nullptr, /* useFullMeasureRest */ false);
    1139              :         }
    1140              : 
    1141              :         // II. Make chord or rest in other track longer if it crosses the insert area
    1142           32 :         if (!measureIsFull) {
    1143           32 :             ChordRest* cr = ms->findCR(tick, track);
    1144           32 :             if (cr && cr->tick() < tick && cr->endTick() > tick) {
    1145            0 :                 if (cr->isRest()) {
    1146            0 :                     const Fraction fillLen = cr->ticks() + fraction;
    1147            0 :                     ms->undoRemoveElement(cr);
    1148            0 :                     ms->setRest(cr->tick(), track, fillLen, /* useDots */ false, /* tuplet */ nullptr, /* useFullMeasureRest */ false);
    1149            0 :                 } else if (cr->isChord()) {
    1150            0 :                     Chord* chord = toChord(cr);
    1151            0 :                     std::vector<TDuration> durations = toDurationList(chord->ticks() + fraction, /* useDots */ true);
    1152            0 :                     Fraction p = chord->tick();
    1153            0 :                     ms->undoRemoveElement(chord);
    1154            0 :                     Chord* prevChord = nullptr;
    1155            0 :                     for (const TDuration& dur : durations) {
    1156            0 :                         Chord* prototype = prevChord ? prevChord : chord;
    1157            0 :                         const bool genTie = bool(prevChord);
    1158            0 :                         prevChord = ms->addChord(p, dur, prototype, genTie, /* tuplet */ nullptr);
    1159            0 :                         p += dur.fraction();
    1160              :                     }
    1161              :                     // TODO: reconnect ties if this chord was tied to other
    1162            0 :                 }
    1163            0 :                 measureIsFull = true;
    1164              :             }
    1165              :         }
    1166              : 
    1167              :         // III. insert rest(s) to fill the inserted space
    1168           32 :         if (!measureIsFull && msMeasure->hasVoice(track)) {
    1169            9 :             ms->setRest(tick, track, fraction, /* useDots */ false, /* tuplet */ nullptr);
    1170              :         }
    1171              :     }
    1172              : 
    1173              :     // Put the note itself.
    1174            5 :     Segment* s = measure->undoGetSegment(SegmentType::ChordRest, tick);
    1175            5 :     Position p(pos);
    1176            5 :     p.segment = s;
    1177            5 :     putNote(tx, score, p, true);
    1178              : 
    1179            5 :     return muse::make_ok();
    1180              : }
    1181              : 
    1182              : //---------------------------------------------------------
    1183              : //   truncateChordRest
    1184              : //---------------------------------------------------------
    1185              : 
    1186            0 : void NoteInput::truncateChordRest(Transaction&, Score* score, ChordRest* cr, const Fraction& tick, bool fillWithRest)
    1187              : {
    1188            0 :     MasterScore* ms = score->masterScore();
    1189            0 :     IF_ASSERT_FAILED(ms && cr && cr->endTick() > tick) {
    1190            0 :         return;
    1191              :     }
    1192              : 
    1193            0 :     const Fraction timeStretch = cr->staff()->timeStretch(cr->tick());
    1194            0 :     Tuplet* tuplet = cr->tuplet();
    1195            0 :     const Fraction overlapDuration = (cr->endTick() - tick) / actualTicks(Fraction(1, 1), tuplet, timeStretch);
    1196            0 :     const Fraction desiredDuration = cr->ticks() - overlapDuration;
    1197              : 
    1198            0 :     ms->changeCRlen(cr, desiredDuration, fillWithRest);
    1199              : 
    1200              :     // Fill the difference with tied notes if necessary...
    1201            0 :     const Fraction difference = desiredDuration - cr->ticks();
    1202            0 :     if (cr->isChord() && difference.isNotZero()) {
    1203            0 :         Fraction startTick = cr->endTick();
    1204            0 :         Chord* prevChord = toChord(cr);
    1205            0 :         const std::vector<TDuration> durationList = toDurationList(difference, true);
    1206            0 :         for (const TDuration& dur : durationList) {
    1207            0 :             prevChord = ms->addChord(startTick, dur, prevChord, /*genTie*/ bool(prevChord), prevChord->tuplet());
    1208            0 :             startTick += actualTicks(dur.fraction(), prevChord->tuplet(), prevChord->staff()->timeStretch(startTick));
    1209              :         }
    1210            0 :     }
    1211              : }
    1212              : 
    1213              : //---------------------------------------------------------
    1214              : //   nextInputPos
    1215              : //---------------------------------------------------------
    1216              : 
    1217           43 : void NoteInput::nextInputPos(Transaction&, Score* score, const ChordRest* cr, bool doSelect)
    1218              : {
    1219           43 :     InputState& is = score->inputState();
    1220              : 
    1221           43 :     ChordRest* ncr = Navigation::nextChordRest(cr);
    1222           43 :     if ((!ncr) && (is.track() % VOICES)) {
    1223            0 :         Segment* s = score->tick2segment(cr->endTick(), false, SegmentType::ChordRest);
    1224            0 :         ncr = s ? toChordRest(s->element(cr->staffIdx() * VOICES)) : nullptr;
    1225              :     }
    1226           43 :     if (ncr) {
    1227           43 :         is.setSegment(ncr->segment());
    1228           43 :         if (doSelect) {
    1229            0 :             score->select(ncr, SelectType::SINGLE, 0);
    1230              :         }
    1231           43 :         for (MuseScoreView* v : score->getViewer()) {
    1232            0 :             v->moveCursor();
    1233              :         }
    1234              :     }
    1235           43 : }
    1236              : 
    1237              : //---------------------------------------------------------
    1238              : //   addPitch
    1239              : ///   insert note or add note to chord
    1240              : //---------------------------------------------------------
    1241              : 
    1242            5 : void NoteInput::addPitch(Transaction& tx, Score* score, const NoteInputParams& params, bool addFlag, bool insert)
    1243              : {
    1244            5 :     InputState& is = score->inputState();
    1245            5 :     if (!is.isValid()) {
    1246            0 :         LOGD("cannot enter notes here (no chord rest at current position)");
    1247            0 :         return;
    1248              :     }
    1249              : 
    1250            5 :     is.setRest(false);
    1251              : 
    1252            5 :     const Drumset* ds = is.drumset();
    1253            5 :     if (ds) {
    1254            0 :         is.setDrumNote(params.drumPitch);
    1255            0 :         is.setVoice(ds->voice(params.drumPitch));
    1256              :     }
    1257              : 
    1258            5 :     addPitch(tx, score, params.step, addFlag, insert);
    1259              : }
    1260              : 
    1261           46 : void NoteInput::addPitch(Transaction& tx, Score* score, int step, bool addFlag, bool insert)
    1262              : {
    1263           46 :     InputState& is = score->inputState();
    1264              : 
    1265           46 :     insert = insert || is.usingNoteEntryMethod(NoteEntryMethod::TIMEWISE);
    1266           46 :     Position pos;
    1267           46 :     if (addFlag) {
    1268           20 :         EngravingItem* el = score->selection().element();
    1269           20 :         if (el && el->isNote()) {
    1270           19 :             Note* selectedNote = toNote(el);
    1271           19 :             Chord* chord  = selectedNote->chord();
    1272           19 :             Segment* seg  = chord->segment();
    1273           19 :             pos.segment   = seg;
    1274           19 :             pos.staffIdx  = chord->vStaffIdx();
    1275           19 :             ClefType clef = score->staff(pos.staffIdx)->clef(seg->tick());
    1276           19 :             pos.line      = relStep(step, clef);
    1277              :             bool error;
    1278           19 :             NoteVal nval = noteValForPosition(score, pos, is.accidentalType(), error);
    1279           19 :             if (error) {
    1280           15 :                 return;
    1281              :             }
    1282            4 :             bool forceAccidental = false;
    1283            4 :             if (is.accidentalType() != AccidentalType::NONE) {
    1284            0 :                 NoteVal nval2 = noteValForPosition(score, pos, AccidentalType::NONE, error);
    1285            0 :                 forceAccidental = (nval.pitch == nval2.pitch);
    1286              :             }
    1287            4 :             if (is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) {
    1288            0 :                 addPitchToChord(tx, score, nval, chord, /* externalInputState */ nullptr, forceAccidental);
    1289              :             } else {
    1290            4 :                 addNote(tx, score, chord, nval, forceAccidental, is.articulationIds());
    1291              :             }
    1292            4 :             is.setAccidentalType(AccidentalType::NONE);
    1293            4 :             return;
    1294              :         }
    1295              :     }
    1296              : 
    1297           27 :     pos.segment   = is.segment();
    1298           27 :     pos.staffIdx  = is.track() / VOICES;
    1299           27 :     ClefType clef = score->staff(pos.staffIdx)->clef(pos.segment->tick());
    1300           27 :     pos.line      = relStep(step, clef);
    1301           27 :     pos.beyondScore = is.beyondScore();
    1302              : 
    1303           27 :     if (is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) {
    1304            0 :         repitchNote(tx, score, pos, !addFlag);
    1305              :     } else {
    1306           27 :         if (insert) {
    1307            5 :             insertChordByInsertingTime(tx, score, pos);
    1308              :         } else {
    1309           22 :             putNote(tx, score, pos, !addFlag);
    1310              :         }
    1311              :     }
    1312           27 :     is.setAccidentalType(AccidentalType::NONE);
    1313              : }
    1314              : 
    1315              : //---------------------------------------------------------
    1316              : //   addFret
    1317              : ///   insert note with given fret on current string
    1318              : //---------------------------------------------------------
    1319              : 
    1320            0 : void NoteInput::addFret(Transaction& tx, Score* score, int fret)
    1321              : {
    1322            0 :     InputState& is = score->inputState();
    1323            0 :     if (is.track() == muse::nidx) { // invalid state
    1324            0 :         return;
    1325              :     }
    1326            0 :     if (!is.segment()) {
    1327            0 :         LOGD("cannot enter notes here (no chord rest at current position)");
    1328            0 :         return;
    1329              :     }
    1330            0 :     is.setRest(false);
    1331            0 :     Position pos;
    1332            0 :     pos.segment   = is.segment();
    1333            0 :     pos.staffIdx  = is.track() / VOICES;
    1334            0 :     pos.line      = score->staff(pos.staffIdx)->staffType(is.tick())->physStringToVisual(is.string());
    1335            0 :     pos.fret      = fret;
    1336            0 :     pos.beyondScore = is.beyondScore();
    1337            0 :     putNote(tx, score, pos, false);
    1338              : }
    1339              : 
    1340              : //---------------------------------------------------------
    1341              : //   noteValueChangeAllowed
    1342              : //    Note value changes are disallowed while a trill cue note is selected outside note entry mode.
    1343              : //---------------------------------------------------------
    1344              : 
    1345            0 : bool NoteInput::noteValueChangeAllowed(const Score* score)
    1346              : {
    1347            0 :     if (score->noteEntryMode()) {
    1348            0 :         return true;
    1349              :     }
    1350              : 
    1351            0 :     for (const ChordRest* cr : score->getSelectedChordRests()) {
    1352            0 :         if (cr->isChord() && toChord(cr)->isTrillCueNote()) {
    1353            0 :             return false;
    1354              :         }
    1355            0 :     }
    1356              : 
    1357            0 :     return true;
    1358              : }
    1359              : 
    1360              : //---------------------------------------------------------
    1361              : //   durationTooShortForDots
    1362              : //    A duration cannot hold `dots` augmentation dots when it is one of the `dots` shortest
    1363              : //    representable durations (the shortest being V_1024TH).
    1364              : //---------------------------------------------------------
    1365              : 
    1366            0 : static bool durationTooShortForDots(const TDuration& duration, int dots)
    1367              : {
    1368              :     // Ordered shortest-first; a duration cannot hold `dots` dots if it is one of the `dots` shortest.
    1369              :     static constexpr DurationType shortestTypes[] = {
    1370              :         DurationType::V_1024TH,
    1371              :         DurationType::V_512TH,
    1372              :         DurationType::V_256TH,
    1373              :         DurationType::V_128TH,
    1374              :     };
    1375              : 
    1376            0 :     const int count = std::min(dots, int(std::size(shortestTypes)));
    1377            0 :     for (int i = 0; i < count; ++i) {
    1378            0 :         if (duration == shortestTypes[i]) {
    1379            0 :             return true;
    1380              :         }
    1381              :     }
    1382              : 
    1383            0 :     return false;
    1384              : }
    1385              : 
    1386              : //---------------------------------------------------------
    1387              : //   setDuration
    1388              : //    Set the note input duration (and apply it to the selection).
    1389              : //---------------------------------------------------------
    1390              : 
    1391            0 : void NoteInput::setDuration(Transaction& tx, Score* score, DurationType duration)
    1392              : {
    1393            0 :     if (!noteValueChangeAllowed(score)) {
    1394            0 :         return;
    1395              :     }
    1396              : 
    1397            0 :     InputState& is = score->inputState();
    1398              : 
    1399            0 :     const TDuration oldDuration = is.duration();
    1400            0 :     const bool oldRest = is.rest();
    1401            0 :     const AccidentalType oldAccidentalType = is.accidentalType();
    1402              : 
    1403            0 :     is.setDuration(duration);
    1404            0 :     is.setDots(0);
    1405              : 
    1406              :     //
    1407              :     // if in "note enter" mode, reset rest flag
    1408              :     //
    1409            0 :     if (score->noteEntryMode()) {
    1410            0 :         if (score->usingNoteEntryMethod(NoteEntryMethod::BY_DURATION) || score->usingNoteEntryMethod(NoteEntryMethod::RHYTHM)) {
    1411              :             // Preserve the number of dots from the previous duration
    1412            0 :             if (oldDuration.dots() > 0) {
    1413            0 :                 toggleDots(tx, score, oldDuration.dots());
    1414              :             }
    1415              : 
    1416            0 :             if (is.rest()) {
    1417              :                 // Enter a rest
    1418            0 :                 score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction());
    1419            0 :                 is.moveToNextInputPos();
    1420              :             } else {
    1421            0 :                 if (score->usingNoteEntryMethod(NoteEntryMethod::RHYTHM)) {
    1422            0 :                     const EngravingItem* selectedItem = score->selection().element();
    1423            0 :                     if (selectedItem && selectedItem->isNote()) {
    1424            0 :                         is.setNotes({ toNote(selectedItem)->noteVal() });
    1425              :                     }
    1426              :                 }
    1427              : 
    1428            0 :                 if (is.beyondScore()) {
    1429            0 :                     score->appendMeasures(1);
    1430            0 :                     is.moveToNextInputPos();
    1431              :                 }
    1432            0 :                 ChordRest* cr = is.cr();
    1433            0 :                 Chord* chord = nullptr;
    1434              : 
    1435            0 :                 if (cr && cr->isChord() && cr->durationType() == is.duration()) {
    1436            0 :                     chord = toChord(cr);
    1437              :                 }
    1438              : 
    1439            0 :                 for (const NoteVal& nval : is.notes()) {
    1440            0 :                     if (chord && chord->findNote(nval.pitch)) {
    1441            0 :                         is.moveToNextInputPos();
    1442            0 :                         continue;
    1443              :                     }
    1444              : 
    1445            0 :                     NoteVal copy(nval);
    1446            0 :                     const Note* note = nullptr;
    1447              : 
    1448            0 :                     if (chord) {
    1449            0 :                         note = addPitchToChord(tx, score, copy, chord);
    1450              :                     } else {
    1451            0 :                         note = addPitch(tx, score, copy, false /*addFlag*/);
    1452              :                     }
    1453              : 
    1454            0 :                     if (note) {
    1455            0 :                         chord = note->chord();
    1456              :                     }
    1457              :                 }
    1458              :             }
    1459              :         } else {
    1460            0 :             is.setRest(false);
    1461              :         }
    1462              :     }
    1463              : 
    1464            0 :     applyToSelection(score, oldDuration, oldRest, oldAccidentalType, false /*toggleForSelectionOnly*/, false /*restToggle*/);
    1465              : }
    1466              : 
    1467              : //---------------------------------------------------------
    1468              : //   toggleRest
    1469              : //    Toggle the note input rest flag (or turn the selection into rests).
    1470              : //---------------------------------------------------------
    1471              : 
    1472            0 : void NoteInput::toggleRest(Transaction& /*tx*/, Score* score)
    1473              : {
    1474            0 :     if (!noteValueChangeAllowed(score)) {
    1475            0 :         return;
    1476              :     }
    1477              : 
    1478            0 :     InputState& is = score->inputState();
    1479              : 
    1480            0 :     const TDuration oldDuration = is.duration();
    1481            0 :     const bool oldRest = is.rest();
    1482            0 :     const AccidentalType oldAccidentalType = is.accidentalType();
    1483              : 
    1484            0 :     if (score->noteEntryMode()) {
    1485            0 :         is.setRest(!is.rest());
    1486            0 :         is.setAccidentalType(AccidentalType::NONE);
    1487            0 :     } else if (score->selection().isNone()) {
    1488            0 :         is.setDuration(DurationType::V_QUARTER);
    1489            0 :         is.setRest(true);
    1490              :     } else {
    1491            0 :         for (ChordRest* cr : score->getSelectedChordRests()) {
    1492            0 :             if (!cr->isRest()) {
    1493            0 :                 score->setNoteRest(cr->segment(), cr->track(), NoteVal(), cr->durationTypeTicks(), DirectionV::AUTO, false,
    1494              :                                    is.articulationIds());
    1495              :             }
    1496            0 :         }
    1497              :     }
    1498              : 
    1499            0 :     applyToSelection(score, oldDuration, oldRest, oldAccidentalType, false /*toggleForSelectionOnly*/, true /*restToggle*/);
    1500              : }
    1501              : 
    1502              : //---------------------------------------------------------
    1503              : //   toggleDots
    1504              : //    Toggle `dots` augmentation dots (1..4) on the note input duration and the selection.
    1505              : //---------------------------------------------------------
    1506              : 
    1507            0 : void NoteInput::toggleDots(Transaction& /*tx*/, Score* score, int dots, bool toggleForSelectionOnly)
    1508              : {
    1509            0 :     IF_ASSERT_FAILED(dots >= 1 && dots <= 4) {
    1510            0 :         return;
    1511              :     }
    1512              : 
    1513            0 :     if (!noteValueChangeAllowed(score)) {
    1514            0 :         return;
    1515              :     }
    1516              : 
    1517            0 :     InputState& is = score->inputState();
    1518              : 
    1519            0 :     const TDuration oldDuration = is.duration();
    1520            0 :     const bool oldRest = is.rest();
    1521            0 :     const AccidentalType oldAccidentalType = is.accidentalType();
    1522              : 
    1523            0 :     if (is.duration().dots() == dots || durationTooShortForDots(is.duration(), dots)) {
    1524            0 :         is.setDots(0);
    1525              :     } else {
    1526            0 :         is.setDots(dots);
    1527              :     }
    1528              : 
    1529            0 :     applyToSelection(score, oldDuration, oldRest, oldAccidentalType, toggleForSelectionOnly, false /*restToggle*/);
    1530              : }
    1531              : 
    1532              : //---------------------------------------------------------
    1533              : //   applyToSelection
    1534              : //    Shared epilogue for setDuration/toggleRest/toggleDots: apply the (possibly changed) input state
    1535              : //    duration/dots to the current selection.
    1536              : //---------------------------------------------------------
    1537              : 
    1538            0 : void NoteInput::applyToSelection(Score* score, const TDuration& oldDuration, bool oldRest,
    1539              :                                  AccidentalType oldAccidentalType, bool toggleForSelectionOnly, bool restToggle)
    1540              : {
    1541            0 :     InputState& is = score->inputState();
    1542              : 
    1543            0 :     if (score->noteEntryMode()) {
    1544            0 :         if (!toggleForSelectionOnly || score->selection().isNone()) {
    1545            0 :             return;
    1546              :         }
    1547              :     }
    1548              : 
    1549            0 :     std::vector<ChordRest*> crs;
    1550            0 :     std::vector<EngravingItem*> elementsToSelect;
    1551              : 
    1552            0 :     if (score->selection().isSingle()) {
    1553            0 :         EngravingItem* e = score->selection().element();
    1554            0 :         ChordRest* cr = InputState::chordRest(e);
    1555              : 
    1556              :         // do not allow to add a dot on a full measure rest
    1557            0 :         if (cr && cr->isRest()) {
    1558            0 :             Rest* r = toRest(cr);
    1559            0 :             if (r->isFullMeasureRest()) {
    1560            0 :                 is.setDots(0);
    1561              :             }
    1562              :         }
    1563              : 
    1564              :         // on measure rest, select the first actual rest
    1565            0 :         if (cr && cr->isMMRest()) {
    1566            0 :             Measure* m = cr->measure()->mmRestFirst();
    1567            0 :             if (m) {
    1568            0 :                 cr = m->findChordRest(m->tick(), 0);
    1569              :             }
    1570              :         }
    1571              : 
    1572            0 :         if (cr) {
    1573            0 :             crs.push_back(cr);
    1574              :         } else {
    1575            0 :             score->deselect(e);
    1576              :         }
    1577            0 :     } else if (score->selection().isNone() && !restToggle) {
    1578            0 :         TDuration td = is.duration();
    1579            0 :         is.setDuration(td);
    1580            0 :         is.setAccidentalType(AccidentalType::NONE);
    1581              :     } else {
    1582            0 :         const auto elements = score->selection().uniqueElements();
    1583            0 :         for (EngravingItem* e : elements) {
    1584            0 :             if (score->selection().isList() || e->isRest() || e->isNote()) {
    1585            0 :                 elementsToSelect.push_back(e);
    1586            0 :                 score->deselect(e);
    1587              :             }
    1588              :         }
    1589            0 :         bool canAdjustLength = true;
    1590            0 :         for (EngravingItem* e : elements) {
    1591            0 :             ChordRest* cr = InputState::chordRest(e);
    1592            0 :             if (!cr) {
    1593            0 :                 continue;
    1594              :             }
    1595            0 :             if (cr->isMeasureRepeat() || cr->isMMRest()) {
    1596            0 :                 canAdjustLength = false;
    1597            0 :                 break;
    1598              :             }
    1599            0 :             crs.push_back(cr);
    1600              :         }
    1601              : 
    1602            0 :         if (canAdjustLength) {
    1603              :             // Change length from last to first chord/rest
    1604            0 :             std::sort(crs.begin(), crs.end(), [](const ChordRest* cr1, const ChordRest* cr2) {
    1605            0 :                 if (cr2->track() == cr1->track()) {
    1606            0 :                     return cr2->isBefore(cr1);
    1607              :                 }
    1608            0 :                 return cr2->track() < cr1->track();
    1609              :             });
    1610              :             // Remove duplicates from the list
    1611            0 :             crs.erase(std::unique(crs.begin(), crs.end()), crs.end());
    1612              :         } else {
    1613            0 :             crs.clear();
    1614              :         }
    1615            0 :     }
    1616              : 
    1617            0 :     const int dots = is.duration().dots();
    1618              : 
    1619            0 :     if (toggleForSelectionOnly && dots > 0 && !crs.empty()) {
    1620            0 :         bool shouldRemoveDots = true;
    1621            0 :         for (const ChordRest* cr : crs) {
    1622            0 :             if (dots != cr->dots()) {
    1623            0 :                 shouldRemoveDots = false;
    1624            0 :                 break;
    1625              :             }
    1626              :         }
    1627              : 
    1628            0 :         if (shouldRemoveDots) {
    1629            0 :             is.setDots(0);
    1630              :         }
    1631              :     }
    1632              : 
    1633            0 :     for (ChordRest* cr : crs) {
    1634            0 :         if (cr->isChord() && (toChord(cr)->isGrace())) {
    1635              :             //
    1636              :             // handle appoggiatura and acciaccatura
    1637              :             //
    1638            0 :             score->undoChangeChordRestLen(cr, is.duration());
    1639              :         } else {
    1640            0 :             score->changeCRlen(cr, is.duration());
    1641              :         }
    1642              :     }
    1643              : 
    1644            0 :     if (!elementsToSelect.empty()) {
    1645            0 :         std::vector<EngravingItem*> selectList;
    1646            0 :         for (EngravingItem* e : elementsToSelect) {
    1647            0 :             if (score->canReselectItem(e)) {
    1648            0 :                 selectList.push_back(e);
    1649              :             }
    1650              :         }
    1651              : 
    1652            0 :         score->select(selectList, SelectType::ADD, 0);
    1653            0 :         score->selection().updateSelectedElements();
    1654            0 :     }
    1655              : 
    1656            0 :     if (toggleForSelectionOnly) {
    1657            0 :         is.setDuration(oldDuration);
    1658            0 :         is.setRest(oldRest);
    1659            0 :         is.setAccidentalType(oldAccidentalType);
    1660              : 
    1661            0 :         if (score->noteEntryMode()) {
    1662            0 :             if (is.lastSegment() == is.segment()) {
    1663            0 :                 is.moveToNextInputPos();
    1664              :             }
    1665              :         }
    1666              :     }
    1667            0 : }
    1668              : 
    1669            0 : void NoteInput::increaseDuration(Transaction& tx, Score* score)
    1670              : {
    1671            0 :     switch (score->inputState().duration().type()) {
    1672              : // cycle back from longest to shortest?
    1673              : //          case TDuration::V_LONG:
    1674              : //                setDuration(tx, score, DurationType::V_128TH);
    1675              : //                break;
    1676            0 :     case DurationType::V_BREVE:
    1677            0 :         setDuration(tx, score, DurationType::V_LONG);
    1678            0 :         break;
    1679            0 :     case DurationType::V_WHOLE:
    1680            0 :         setDuration(tx, score, DurationType::V_BREVE);
    1681            0 :         break;
    1682            0 :     case DurationType::V_HALF:
    1683            0 :         setDuration(tx, score, DurationType::V_WHOLE);
    1684            0 :         break;
    1685            0 :     case DurationType::V_QUARTER:
    1686            0 :         setDuration(tx, score, DurationType::V_HALF);
    1687            0 :         break;
    1688            0 :     case DurationType::V_EIGHTH:
    1689            0 :         setDuration(tx, score, DurationType::V_QUARTER);
    1690            0 :         break;
    1691            0 :     case DurationType::V_16TH:
    1692            0 :         setDuration(tx, score, DurationType::V_EIGHTH);
    1693            0 :         break;
    1694            0 :     case DurationType::V_32ND:
    1695            0 :         setDuration(tx, score, DurationType::V_16TH);
    1696            0 :         break;
    1697            0 :     case DurationType::V_64TH:
    1698            0 :         setDuration(tx, score, DurationType::V_32ND);
    1699            0 :         break;
    1700            0 :     case DurationType::V_128TH:
    1701            0 :         setDuration(tx, score, DurationType::V_64TH);
    1702            0 :         break;
    1703            0 :     case DurationType::V_256TH:
    1704            0 :         setDuration(tx, score, DurationType::V_128TH);
    1705            0 :         break;
    1706            0 :     case DurationType::V_512TH:
    1707            0 :         setDuration(tx, score, DurationType::V_256TH);
    1708            0 :         break;
    1709            0 :     case DurationType::V_1024TH:
    1710            0 :         setDuration(tx, score, DurationType::V_512TH);
    1711            0 :         break;
    1712            0 :     default:
    1713            0 :         break;
    1714              :     }
    1715            0 : }
    1716              : 
    1717            0 : void NoteInput::decreaseDuration(Transaction& tx, Score* score)
    1718              : {
    1719            0 :     switch (score->inputState().duration().type()) {
    1720            0 :     case DurationType::V_LONG:
    1721            0 :         setDuration(tx, score, DurationType::V_BREVE);
    1722            0 :         break;
    1723            0 :     case DurationType::V_BREVE:
    1724            0 :         setDuration(tx, score, DurationType::V_WHOLE);
    1725            0 :         break;
    1726            0 :     case DurationType::V_WHOLE:
    1727            0 :         setDuration(tx, score, DurationType::V_HALF);
    1728            0 :         break;
    1729            0 :     case DurationType::V_HALF:
    1730            0 :         setDuration(tx, score, DurationType::V_QUARTER);
    1731            0 :         break;
    1732            0 :     case DurationType::V_QUARTER:
    1733            0 :         setDuration(tx, score, DurationType::V_EIGHTH);
    1734            0 :         break;
    1735            0 :     case DurationType::V_EIGHTH:
    1736            0 :         setDuration(tx, score, DurationType::V_16TH);
    1737            0 :         break;
    1738            0 :     case DurationType::V_16TH:
    1739            0 :         setDuration(tx, score, DurationType::V_32ND);
    1740            0 :         break;
    1741            0 :     case DurationType::V_32ND:
    1742            0 :         setDuration(tx, score, DurationType::V_64TH);
    1743            0 :         break;
    1744            0 :     case DurationType::V_64TH:
    1745            0 :         setDuration(tx, score, DurationType::V_128TH);
    1746            0 :         break;
    1747            0 :     case DurationType::V_128TH:
    1748            0 :         setDuration(tx, score, DurationType::V_256TH);
    1749            0 :         break;
    1750            0 :     case DurationType::V_256TH:
    1751            0 :         setDuration(tx, score, DurationType::V_512TH);
    1752            0 :         break;
    1753            0 :     case DurationType::V_512TH:
    1754            0 :         setDuration(tx, score, DurationType::V_1024TH);
    1755            0 :         break;
    1756              : // cycle back from shortest to longest?
    1757              : //          case DurationType::V_1024TH:
    1758              : //                setDuration(tx, score, DurationType::V_LONG);
    1759              : //                break;
    1760            0 :     default:
    1761            0 :         break;
    1762              :     }
    1763            0 : }
    1764              : 
    1765              : //---------------------------------------------------------
    1766              : //   realtimeAdvance
    1767              : //---------------------------------------------------------
    1768              : 
    1769            0 : void NoteInput::realtimeAdvance(Transaction& tx, Score* score, bool allowTransposition, const std::vector<int>& activeMidiPitches)
    1770              : {
    1771            0 :     InputState& is = score->inputState();
    1772            0 :     if (!is.noteEntryMode()) {
    1773            0 :         return;
    1774              :     }
    1775              : 
    1776            0 :     Fraction ticks2measureEnd = is.segment()->measure()->ticks() - is.segment()->rtick();
    1777            0 :     if (!is.cr() || (is.cr()->ticks() != is.duration().fraction() && is.duration() < ticks2measureEnd)) {
    1778            0 :         score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction(), DirectionV::AUTO);
    1779              :     }
    1780              : 
    1781            0 :     ChordRest* prevCR = toChordRest(is.cr());
    1782            0 :     if (is.beyondScore()) {
    1783            0 :         score->appendMeasures(1);
    1784              :     }
    1785              : 
    1786            0 :     is.moveToNextInputPos();
    1787              : 
    1788            0 :     if (activeMidiPitches.empty()) {
    1789            0 :         score->setNoteRest(is.segment(), is.track(), NoteVal(), is.duration().fraction(), DirectionV::AUTO);
    1790              :     } else {
    1791            0 :         Chord* prevChord = prevCR->isChord() ? toChord(prevCR) : 0;
    1792            0 :         bool partOfChord = false;
    1793            0 :         for (int pitch : activeMidiPitches) {
    1794            0 :             NoteInput::addTiedMidiPitch(tx, score, pitch, partOfChord, prevChord, allowTransposition);
    1795            0 :             partOfChord = true;
    1796              :         }
    1797              :     }
    1798              : 
    1799            0 :     if (prevCR->measure() != is.segment()->measure()) {
    1800              :         // just advanced across barline. Now simplify tied notes.
    1801            0 :         Regroup::regroupNotesAndRests(tx, score, prevCR->measure()->tick(), is.segment()->measure()->tick(), is.track());
    1802              :     }
    1803              : }
        

Generated by: LCOV version 2.0-1