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 : }
|