/* * SPDX-License-Identifier: GPL-3.0-only * MuseScore-CLA-applies * * MuseScore * Music Composition & Notation * * Copyright (C) 2021 MuseScore BVBA and others * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "translation.h" #include "interactive/messagebox.h" #include "factory.h" #include "utils.h" #include "score.h" #include "chord.h" #include "measure.h" #include "tie.h" #include "tuplet.h" #include "staff.h" #include "part.h" #include "drumset.h" #include "slur.h" #include "navigate.h" #include "stringdata.h" #include "undo.h" #include "range.h" #include "excerpt.h" #include "accidental.h" #include "measurerepeat.h" #include "masterscore.h" #include "log.h" using namespace mu; using namespace mu::engraving; namespace Ms { //--------------------------------------------------------- // noteValForPosition //--------------------------------------------------------- NoteVal Score::noteValForPosition(Position pos, AccidentalType at, bool& error) { error = false; Segment* s = pos.segment; int line = pos.line; Fraction tick = s->tick(); staff_idx_t staffIdx = pos.staffIdx; Staff* st = staff(staffIdx); ClefType clef = st->clef(tick); const Instrument* instr = st->part()->instrument(s->tick()); NoteVal nval; const StringData* stringData = 0; // pitched/unpitched note entry depends on instrument (override StaffGroup) StaffGroup staffGroup = st->staffType(tick)->group(); if (staffGroup != StaffGroup::TAB) { staffGroup = instr->useDrumset() ? StaffGroup::PERCUSSION : StaffGroup::STANDARD; } switch (staffGroup) { case StaffGroup::PERCUSSION: { if (_is.rest()) { break; } const Drumset* ds = instr->drumset(); nval.pitch = _is.drumNote(); if (nval.pitch < 0) { error = true; return nval; } nval.headGroup = ds->noteHead(nval.pitch); if (nval.headGroup == NoteHeadGroup::HEAD_INVALID) { error = true; return nval; } break; } case StaffGroup::TAB: { if (_is.rest()) { error = true; return nval; } stringData = instr->stringData(); line = st->staffType(tick)->visualStringToPhys(line); if (line < 0 || line >= static_cast(stringData->strings())) { error = true; return nval; } // build a default NoteVal for that string nval.string = line; if (pos.fret != INVALID_FRET_INDEX) { // if a fret is given, use it nval.fret = pos.fret; } else { // if no fret, use 0 as default _is.setString(line); nval.fret = 0; } // reduce within fret limit if (nval.fret > stringData->frets()) { nval.fret = stringData->frets(); } // for open strings, only accepts fret 0 (strings in StringData are from bottom to top) size_t strgDataIdx = stringData->strings() - line - 1; if (nval.fret > 0 && stringData->stringList().at(strgDataIdx).open == true) { nval.fret = 0; } nval.pitch = stringData->getPitch(line, nval.fret, st); break; } case StaffGroup::STANDARD: { AccidentalVal acci = (at == AccidentalType::NONE ? s->measure()->findAccidental(s, staffIdx, line, error) : Accidental::subtype2value(at)); if (error) { return nval; } int step = absStep(line, clef); int octave = step / 7; nval.pitch = step2pitch(step) + octave * 12 + int(acci); if (styleB(Sid::concertPitch)) { nval.tpc1 = step2tpc(step % 7, acci); } else { nval.pitch += instr->transpose().chromatic; nval.tpc2 = step2tpc(step % 7, acci); Interval v = st->part()->instrument(tick)->transpose(); if (v.isZero()) { nval.tpc1 = nval.tpc2; } else { nval.tpc1 = Ms::transposeTpc(nval.tpc2, v, true); } } } break; } return nval; } //--------------------------------------------------------- // addPitch //--------------------------------------------------------- Note* Score::addPitch(NoteVal& nval, bool addFlag, InputState* externalInputState) { InputState& is = externalInputState ? (*externalInputState) : _is; if (addFlag) { ChordRest* c = toChordRest(is.lastSegment()->element(is.track())); if (c == 0 || !c->isChord()) { LOGD("Score::addPitch: cr %s", c ? c->typeName() : "zero"); return 0; } Note* note = addNote(toChord(c), nval, /* forceAccidental */ false, is.articulationIds(), externalInputState); if (is.lastSegment() == is.segment()) { NoteEntryMethod entryMethod = is.noteEntryMethod(); if (entryMethod != NoteEntryMethod::REALTIME_AUTO && entryMethod != NoteEntryMethod::REALTIME_MANUAL) { is.moveToNextInputPos(); } } return note; } expandVoice(is.segment(), is.track()); // insert note DirectionV stemDirection = DirectionV::AUTO; track_idx_t track = is.track(); if (is.drumNote() != -1) { nval.pitch = is.drumNote(); const Drumset* ds = is.drumset(); nval.headGroup = ds->noteHead(nval.pitch); stemDirection = ds->stemDirection(nval.pitch); track = ds->voice(nval.pitch) + (is.track() / VOICES) * VOICES; is.setTrack(track); expandVoice(is.segment(), is.track()); } if (!is.cr()) { return 0; } Measure* measure = is.segment()->measure(); if (measure->isMeasureRepeatGroup(track2staff(track))) { MeasureRepeat* mr = measure->measureRepeatElement(track2staff(track)); deleteItem(mr); // resets any measures related to mr } Fraction duration; if (is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) { duration = is.cr()->ticks(); } else if (is.usingNoteEntryMethod(NoteEntryMethod::REALTIME_AUTO) || is.usingNoteEntryMethod(NoteEntryMethod::REALTIME_MANUAL)) { // FIXME: truncate duration at barline in real-time modes. // The user might try to enter a duration that is too long to fit in the remaining space in the measure. // We could split the duration at the barline and continue into the next bar, but this would create extra // notes, extra ties, and extra pain. Instead, we simply truncate the duration at the barline. Fraction ticks2measureEnd = is.segment()->measure()->ticks() - is.segment()->rtick(); duration = is.duration().fraction() > ticks2measureEnd ? ticks2measureEnd : is.duration().fraction(); } else { duration = is.duration().fraction(); } Note* note = 0; Note* firstTiedNote = 0; Note* lastTiedNote = 0; if (is.usingNoteEntryMethod(NoteEntryMethod::REPITCH) && is.cr()->isChord()) { // repitch mode for MIDI input (where we are given a pitch) is handled here // for keyboard input (where we are given a staff position), there is a separate function Score::repitchNote() // the code is similar enough that it could possibly be refactored Chord* chord = toChord(is.cr()); note = Factory::createNote(chord); note->setParent(chord); note->setTrack(chord->track()); note->setNval(nval); lastTiedNote = note; if (!addFlag) { std::vector notes = chord->notes(); // break all ties into current chord // these will exist only if user explicitly moved cursor to a tied-into note // in ordinary use, cursor will automatically skip past these during note entry for (Note* n : notes) { if (n->tieBack()) { undoRemoveElement(n->tieBack()); } } // for single note chords only, preserve ties by changing pitch of all forward notes // the tie forward itself will be added later // multi-note chords get reduced to single note chords anyhow since we remove the old notes below // so there will be no way to preserve those ties if (notes.size() == 1 && notes.front()->tieFor()) { Note* tn = notes.front()->tieFor()->endNote(); while (tn) { Chord* tc = tn->chord(); if (tc->notes().size() != 1) { undoRemoveElement(tn->tieBack()); break; } if (!firstTiedNote) { firstTiedNote = tn; } lastTiedNote = tn; undoChangePitch(tn, note->pitch(), note->tpc1(), note->tpc2()); if (tn->tieFor()) { tn = tn->tieFor()->endNote(); } else { break; } } } // remove all notes from chord // the new note will be added below while (!chord->notes().empty()) { undoRemoveElement(chord->notes().front()); } } // add new note to chord undoAddElement(note); setPlayNote(true); // recreate tie forward if there is a note to tie to // one-sided ties will not be recreated if (firstTiedNote) { Tie* tie = Factory::createTie(note); tie->setStartNote(note); tie->setEndNote(firstTiedNote); tie->setTick(tie->startNote()->tick()); tie->setTick2(tie->endNote()->tick()); tie->setTrack(note->track()); undoAddElement(tie); } select(lastTiedNote); } else if (!is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) { Segment* seg = setNoteRest( is.segment(), track, nval, duration, stemDirection, /* forceAccidental */ false, {}, /* rhythmic */ false, externalInputState); if (seg) { note = toChord(seg->element(track))->upNote(); } } if (is.slur()) { // // extend slur // ChordRest* e = searchNote(is.tick(), is.track()); if (e) { Fraction stick = Fraction(0, 1); EngravingItem* ee = is.slur()->startElement(); if (ee->isChordRest()) { stick = toChordRest(ee)->tick(); } else if (ee->isNote()) { stick = toNote(ee)->chord()->tick(); } if (stick == e->tick()) { is.slur()->setTick(stick); is.slur()->setStartElement(e); } else { is.slur()->setTick2(e->tick()); is.slur()->setEndElement(e); } } else { LOGD("addPitch: cannot find slur note"); } } if (is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) { // move cursor to next note, but skip tied notes (they were already repitched above) ChordRest* next = lastTiedNote ? nextChordRest(lastTiedNote->chord()) : nextChordRest(is.cr()); while (next && !next->isChord()) { next = nextChordRest(next); } if (next) { is.moveInputPos(next->segment()); } } else { NoteEntryMethod entryMethod = is.noteEntryMethod(); if (entryMethod != NoteEntryMethod::REALTIME_AUTO && entryMethod != NoteEntryMethod::REALTIME_MANUAL) { is.moveToNextInputPos(); } } return note; } //--------------------------------------------------------- // putNote // mouse click in state NoteType::ENTRY //--------------------------------------------------------- void Score::putNote(const PointF& pos, bool replace, bool insert) { Position p; if (!getPosition(&p, pos, _is.voice())) { LOGD("cannot put note here, get position failed"); return; } Score* score = p.segment->score(); // it is not safe to call Score::repitchNote() if p is on a TAB staff bool isTablature = staff(p.staffIdx)->isTabStaff(p.segment->tick()); // calculate actual clicked line from staffType offset and stepOffset Staff* ss = score->staff(p.staffIdx); int stepOffset = ss->staffType(p.segment->tick())->stepOffset(); qreal stYOffset = ss->staffType(p.segment->tick())->yoffset().val(); qreal lineDist = ss->staffType(p.segment->tick())->lineDistance().val(); p.line -= stepOffset + 2 * stYOffset / lineDist; if (score->inputState().usingNoteEntryMethod(NoteEntryMethod::REPITCH) && !isTablature) { score->repitchNote(p, replace); } else { if (insert || score->inputState().usingNoteEntryMethod(NoteEntryMethod::TIMEWISE)) { score->insertChord(p); } else { score->putNote(p, replace); } } } void Score::putNote(const Position& p, bool replace) { Staff* st = staff(p.staffIdx); Segment* s = p.segment; _is.setTrack(p.staffIdx * VOICES + _is.voice()); _is.setSegment(s); if (score()->excerpt() && !score()->excerpt()->tracksMapping().empty() && mu::key(score()->excerpt()->tracksMapping(), _is.track(), mu::nidx) == mu::nidx) { return; } DirectionV stemDirection = DirectionV::AUTO; bool error; NoteVal nval = noteValForPosition(p, _is.accidentalType(), error); if (error) { return; } // warn and delete MeasureRepeat if necessary Measure* m = _is.segment()->measure(); staff_idx_t staffIdx = track2staff(_is.track()); if (m->isMeasureRepeatGroup(staffIdx)) { auto b = MessageBox::warning(trc("engraving", "Note input will remove measure repeat"), trc("engraving", "There is a measure repeat here.") + trc("engraving", "\nContinue with adding note and delete measure repeat?")); if (b == MessageBox::Cancel) { return; } Score::deleteItem(m->measureRepeatElement(staffIdx)); } const StringData* stringData = 0; // pitched/unpitched note entry depends on instrument (override StaffGroup) StaffGroup staffGroup = st->staffType(s->tick())->group(); if (staffGroup != StaffGroup::TAB) { staffGroup = st->part()->instrument(s->tick())->useDrumset() ? StaffGroup::PERCUSSION : StaffGroup::STANDARD; } switch (staffGroup) { case StaffGroup::PERCUSSION: { const Drumset* ds = st->part()->instrument(s->tick())->drumset(); stemDirection = ds->stemDirection(nval.pitch); break; } case StaffGroup::TAB: stringData = st->part()->instrument(s->tick())->stringData(); _is.setDrumNote(-1); break; case StaffGroup::STANDARD: _is.setDrumNote(-1); break; } expandVoice(); ChordRest* cr = _is.cr(); bool addToChord = false; if (cr) { // retrieve total duration of current chord TDuration d = cr->durationType(); // if not in replace mode AND chord duration == input duration AND not rest input // we need to add to current chord (otherwise, we will need to replace it or create a new one) if (!replace && (d == _is.duration()) && cr->isChord() && !_is.rest()) { if (st->isTabStaff(cr->tick())) { // TAB // if a note on same string already exists, update to new pitch/fret foreach (Note* note, toChord(cr)->notes()) { if (note->string() == nval.string) { // if string is the same // if adding a new digit will keep fret number within fret limit, // add a digit to existing fret number if (stringData) { int fret = note->fret() * 10 + nval.fret; if (fret <= stringData->frets()) { nval.fret = fret; nval.pitch = stringData->getPitch(nval.string, nval.fret, st); } else { LOGD("can't increase fret to %d", fret); } } // set fret number (original or combined) in all linked notes int tpc1 = note->tpc1default(nval.pitch); int tpc2 = note->tpc2default(nval.pitch); undoChangeFretting(note, nval.pitch, nval.string, nval.fret, tpc1, tpc2); setPlayNote(true); return; } } } else { // not TAB // if a note with the same pitch already exists in the chord, remove it Chord* chord = toChord(cr); Note* note = chord->findNote(nval.pitch); if (note) { if (chord->notes().size() > 1) { undoRemoveElement(note); } return; } } addToChord = true; // if no special case, add note to chord } } bool forceAccidental = false; if (_is.accidentalType() != AccidentalType::NONE) { NoteVal nval2 = noteValForPosition(p, AccidentalType::NONE, error); forceAccidental = (nval.pitch == nval2.pitch); } if (addToChord && cr->isChord()) { // if adding, add! addNote(toChord(cr), nval, forceAccidental, _is.articulationIds()); _is.setAccidentalType(AccidentalType::NONE); return; } else { // if not adding, replace current chord (or create a new one) if (_is.rest()) { nval.pitch = -1; } setNoteRest(_is.segment(), _is.track(), nval, _is.duration().fraction(), stemDirection, forceAccidental, _is.articulationIds()); _is.setAccidentalType(AccidentalType::NONE); } if (cr && !st->isTabStaff(cr->tick())) { _is.moveToNextInputPos(); } } //--------------------------------------------------------- // repitchNote //--------------------------------------------------------- void Score::repitchNote(const Position& p, bool replace) { Segment* s = p.segment; Fraction tick = s->tick(); Staff* st = staff(p.staffIdx); ClefType clef = st->clef(tick); NoteVal nval; bool error = false; AccidentalType at = _is.accidentalType(); if (_is.drumset() && _is.drumNote() != -1) { nval.pitch = _is.drumNote(); } else { AccidentalVal acci = (at == AccidentalType::NONE ? s->measure()->findAccidental(s, p.staffIdx, p.line, error) : Accidental::subtype2value(at)); if (error) { return; } int step = absStep(p.line, clef); int octave = step / 7; nval.pitch = step2pitch(step) + octave * 12 + int(acci); if (styleB(Sid::concertPitch)) { nval.tpc1 = step2tpc(step % 7, acci); } else { nval.pitch += st->part()->instrument(s->tick())->transpose().chromatic; nval.tpc2 = step2tpc(step % 7, acci); } } if (!_is.segment()) { return; } Chord* chord; ChordRest* cr = _is.cr(); if (!cr) { cr = _is.segment()->nextChordRest(_is.track()); if (!cr) { return; } } if (cr->isRest()) { //skip rests ChordRest* next = nextChordRest(cr); while (next && !next->isChord()) { next = nextChordRest(next); } if (next) { _is.moveInputPos(next->segment()); } return; } else { chord = toChord(cr); } Note* note = Factory::createNote(chord); note->setParent(chord); note->setTrack(chord->track()); note->setNval(nval); Note* firstTiedNote = 0; Note* lastTiedNote = note; if (replace) { std::vector notes = chord->notes(); // break all ties into current chord // these will exist only if user explicitly moved cursor to a tied-into note // in ordinary use, cursor will automatically skip past these during note entry for (Note* n : notes) { if (n->tieBack()) { undoRemoveElement(n->tieBack()); } } // for single note chords only, preserve ties by changing pitch of all forward notes // the tie forward itself will be added later // multi-note chords get reduced to single note chords anyhow since we remove the old notes below // so there will be no way to preserve those ties if (notes.size() == 1 && notes.front()->tieFor()) { Note* tn = notes.front()->tieFor()->endNote(); while (tn) { Chord* tc = tn->chord(); if (tc->notes().size() != 1) { undoRemoveElement(tn->tieBack()); break; } if (!firstTiedNote) { firstTiedNote = tn; } lastTiedNote = tn; undoChangePitch(tn, note->pitch(), note->tpc1(), note->tpc2()); if (tn->tieFor()) { tn = tn->tieFor()->endNote(); } else { break; } } } // remove all notes from chord // the new note will be added below while (!chord->notes().empty()) { undoRemoveElement(chord->notes().front()); } } // add new note to chord undoAddElement(note); bool forceAccidental = false; if (_is.accidentalType() != AccidentalType::NONE) { NoteVal nval2 = noteValForPosition(p, AccidentalType::NONE, error); forceAccidental = (nval.pitch == nval2.pitch); } if (forceAccidental) { int tpc = styleB(Sid::concertPitch) ? nval.tpc1 : nval.tpc2; AccidentalVal alter = tpc2alter(tpc); at = Accidental::value2subtype(alter); Accidental* a = Factory::createAccidental(note); a->setAccidentalType(at); a->setRole(AccidentalRole::USER); a->setParent(note); undoAddElement(a); } setPlayNote(true); setPlayChord(true); // recreate tie forward if there is a note to tie to // one-sided ties will not be recreated if (firstTiedNote) { Tie* tie = Factory::createTie(note); tie->setStartNote(note); tie->setEndNote(firstTiedNote); tie->setTick(tie->startNote()->tick()); tie->setTick2(tie->endNote()->tick()); tie->setTrack(note->track()); undoAddElement(tie); } select(lastTiedNote); // move to next Chord ChordRest* next = nextChordRest(lastTiedNote->chord()); while (next && !next->isChord()) { next = nextChordRest(next); } if (next) { _is.moveInputPos(next->segment()); } } //--------------------------------------------------------- // insertChord //--------------------------------------------------------- void Score::insertChord(const Position& pos) { // insert // TODO: // - check voices // - split chord/rest EngravingItem* el = selection().element(); if (!el || !(el->isNote() || el->isRest())) { return; } Segment* seg = pos.segment; if (seg->splitsTuplet()) { MScore::setError(MsError::CANNOT_INSERT_TUPLET); return; } if (_is.insertMode()) { globalInsertChord(pos); } else { localInsertChord(pos); } } //--------------------------------------------------------- // localInsertChord //--------------------------------------------------------- void Score::localInsertChord(const Position& pos) { const TDuration duration = _is.duration(); const Fraction fraction = duration.fraction(); const Fraction len = fraction; Segment* seg = pos.segment; Fraction tick = seg->tick(); Measure* measure = seg->measure()->isMMRest() ? seg->measure()->mmRestFirst() : seg->measure(); const Fraction targetMeasureLen = measure->ticks() + fraction; // Shift spanners, enlarge the measure. // The approach is similar to that in Measure::adjustToLen() but does // insert time to the middle of the measure rather than to the end. undoInsertTime(tick, len); undo(new InsertTime(this, tick, len)); for (Score* score : scoreList()) { Measure* m = score->tick2measure(tick); undo(new ChangeMeasureLen(m, targetMeasureLen)); Segment* scoreSeg = m->tick2segment(tick); for (Segment* s = scoreSeg; s; s = s->next()) { s->undoChangeProperty(Pid::TICK, s->rtick() + len); } } // Fill the inserted time with rests. // This is better to be done in master score to cover all staves. MasterScore* ms = masterScore(); Measure* msMeasure = ms->tick2measure(tick); const size_t msTracks = ms->ntracks(); Segment* firstSeg = msMeasure->first(SegmentType::ChordRest); for (track_idx_t track = 0; track < msTracks; ++track) { EngravingItem* maybeRest = firstSeg->element(track); bool measureIsFull = false; // I. Convert any measure rests into normal (non-measure) rest(s) of equivalent duration if (maybeRest && maybeRest->isRest() && toRest(maybeRest)->durationType().isMeasure()) { ms->undoRemoveElement(maybeRest); Rest* measureRest = toRest(maybeRest); // If measure rest is situated at measure start we will fill // the whole measure with rests. measureIsFull = measureRest->rtick().isZero(); const Fraction fillLen = measureIsFull ? targetMeasureLen : measureRest->ticks(); ms->setRest(measureRest->tick(), track, fillLen, /* useDots */ false, /* tuplet */ nullptr, /* useFullMeasureRest */ false); } // II. Make chord or rest in other track longer if it crosses the insert area if (!measureIsFull) { ChordRest* cr = ms->findCR(tick, track); if (cr && cr->tick() < tick && (cr->tick() + cr->actualTicks()) > tick) { if (cr->isRest()) { const Fraction fillLen = cr->ticks() + fraction; ms->undoRemoveElement(cr); ms->setRest(cr->tick(), track, fillLen, /* useDots */ false, /* tuplet */ nullptr, /* useFullMeasureRest */ false); } else if (cr->isChord()) { Chord* chord = toChord(cr); std::vector durations = toDurationList(chord->ticks() + fraction, /* useDots */ true); Fraction p = chord->tick(); ms->undoRemoveElement(chord); Chord* prevChord = nullptr; for (const TDuration& dur : durations) { Chord* prototype = prevChord ? prevChord : chord; const bool genTie = bool(prevChord); prevChord = ms->addChord(p, dur, prototype, genTie, /* tuplet */ nullptr); p += dur.fraction(); } // TODO: reconnect ties if this chord was tied to other } measureIsFull = true; } } // III. insert rest(s) to fill the inserted space if (!measureIsFull && msMeasure->hasVoice(track)) { ms->setRest(tick, track, fraction, /* useDots */ false, /* tuplet */ nullptr); } } // Put the note itself. Segment* s = measure->undoGetSegment(SegmentType::ChordRest, tick); Position p(pos); p.segment = s; putNote(p, true); } //--------------------------------------------------------- // globalInsertChord //--------------------------------------------------------- void Score::globalInsertChord(const Position& pos) { ChordRest* cr = selection().cr(); track_idx_t track = cr ? cr->track() : mu::nidx; deselectAll(); Segment* s1 = pos.segment; Segment* s2 = lastSegment(); TDuration duration = _is.duration(); Fraction fraction = duration.fraction(); ScoreRange r; r.read(s1, s2, false); track_idx_t strack = 0; // for now for all tracks track_idx_t etrack = nstaves() * VOICES; Fraction stick = s1->tick(); Fraction etick = s2->tick(); Fraction ticks = fraction; Fraction len = r.ticks(); if (!r.truncate(fraction)) { appendMeasures(1); } putNote(pos, true); Fraction dtick = s1->tick() + ticks; int voiceOffsets[VOICES] { 0, 0, 0, 0 }; len = r.ticks(); for (size_t staffIdx = 0; staffIdx < nstaves(); ++staffIdx) { makeGap1(dtick, staffIdx, r.ticks(), voiceOffsets); } r.write(this, dtick); for (auto i : spanner()) { Spanner* s = i.second; if (s->track() >= strack && s->track() < etrack) { if (s->tick() >= stick && s->tick() < etick) { s->undoChangeProperty(Pid::SPANNER_TICK, s->tick() + ticks); } else if (s->tick2() >= stick && s->tick2() < etick) { s->undoChangeProperty(Pid::SPANNER_TICKS, s->ticks() + ticks); } } } if (track != mu::nidx) { Measure* m = tick2measure(dtick); Segment* s = m->findSegment(SegmentType::ChordRest, dtick); EngravingItem* e = s->element(track); if (e) { select(e->isChord() ? toChord(e)->notes().front() : e); } } } } // namespace Ms