//============================================================================= // MuseScore // Music Composition & Notation // // Copyright (C) 2002-2013 Werner Schweer // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 // as published by the Free Software Foundation and appearing in // the file LICENCE.GPL //============================================================================= /** \file Handling of several GUI commands. */ #include #include "types.h" #include "musescoreCore.h" #include "score.h" #include "utils.h" #include "key.h" #include "clef.h" #include "navigate.h" #include "slur.h" #include "tie.h" #include "note.h" #include "rest.h" #include "chord.h" #include "text.h" #include "sig.h" #include "staff.h" #include "part.h" #include "style.h" #include "page.h" #include "barline.h" #include "tuplet.h" #include "xml.h" #include "ottava.h" #include "trill.h" #include "pedal.h" #include "hairpin.h" #include "textline.h" #include "keysig.h" #include "volta.h" #include "dynamic.h" #include "box.h" #include "harmony.h" #include "system.h" #include "stafftext.h" #include "articulation.h" #include "layoutbreak.h" #include "drumset.h" #include "beam.h" #include "lyrics.h" #include "pitchspelling.h" #include "measure.h" #include "tempo.h" #include "undo.h" #include "timesig.h" #include "repeat.h" #include "tempotext.h" #include "noteevent.h" #include "breath.h" #include "stringdata.h" #include "stafftype.h" #include "segment.h" #include "chordlist.h" #include "mscore.h" #include "accidental.h" #include "sequencer.h" #include "tremolo.h" #include "rehearsalmark.h" #include "sym.h" namespace Ms { //--------------------------------------------------------- // reset //--------------------------------------------------------- void CmdState::reset() { layoutFlags = LayoutFlag::NO_FLAGS; _updateMode = UpdateMode::DoNothing; _startTick = Fraction(-1,1); _endTick = Fraction(-1,1); _startStaff = -1; _endStaff = -1; _el = nullptr; _oneElement = true; _elIsMeasureBase = false; _locked = false; } //--------------------------------------------------------- // setTick //--------------------------------------------------------- void CmdState::setTick(const Fraction& t) { if (_locked) return; if (_startTick == Fraction(-1,1) || t < _startTick) _startTick = t; if (_endTick == Fraction(-1,1) || t > _endTick) _endTick = t; setUpdateMode(UpdateMode::Layout); } //--------------------------------------------------------- // setStaff //--------------------------------------------------------- void CmdState::setStaff(int st) { if (_locked || st == -1) return; if (_startStaff == -1 || st < _startStaff) _startStaff = st; if (_endStaff == -1 || st > _endStaff) _endStaff = st; } //--------------------------------------------------------- // setElement //--------------------------------------------------------- void CmdState::setElement(const Element* e) { if (!e || _el == e || _locked) return; // prefer measures and frames as edit targets const bool oldIsMeasureBase = _elIsMeasureBase; const bool newIsMeasureBase = e->isMeasureBase(); if (newIsMeasureBase && !oldIsMeasureBase) { _oneElement = true; return; } else if (oldIsMeasureBase && !newIsMeasureBase) return; // don't remember the new element else _oneElement = !_el; _el = e; _elIsMeasureBase = newIsMeasureBase; } //--------------------------------------------------------- // unsetElement //--------------------------------------------------------- void CmdState::unsetElement(const Element* e) { if (_el == e) _el = nullptr; } //--------------------------------------------------------- // setUpdateMode //--------------------------------------------------------- void CmdState::_setUpdateMode(UpdateMode m) { _updateMode = m; } void CmdState::setUpdateMode(UpdateMode m) { if (int(m) > int(_updateMode)) _setUpdateMode(m); } //--------------------------------------------------------- // startCmd /// Start a GUI command by clearing the redraw area /// and starting a user-visible undo. //--------------------------------------------------------- void Score::startCmd() { if (MScore::debugMode) qDebug("===startCmd()"); cmdState().reset(); // Start collecting low-level undo operations for a // user-visible undo action. if (undoStack()->active()) { qDebug("Score::startCmd(): cmd already active"); return; } undoStack()->beginMacro(this); } //--------------------------------------------------------- // undoRedo //--------------------------------------------------------- void Score::undoRedo(bool undo, EditData* ed) { if (readOnly()) return; cmdState().reset(); if (undo) undoStack()->undo(ed); else undoStack()->redo(ed); update(false); masterScore()->setPlaylistDirty(); // TODO: flag all individual operations updateSelection(); } //--------------------------------------------------------- // endCmd /// End a GUI command by (if \a undo) ending a user-visble undo /// and (always) updating the redraw area. //--------------------------------------------------------- void Score::endCmd(bool rollback) { if (!undoStack()->active()) { qDebug("Score::endCmd(): no cmd active"); update(); return; } if (readOnly() || MScore::_error != MS_NO_ERROR) rollback = true; if (rollback) undoStack()->current()->unwind(); update(false); if (MScore::debugMode) qDebug("===endCmd() %d", undoStack()->current()->childCount()); const bool noUndo = undoStack()->current()->empty(); // nothing to undo? undoStack()->endMacro(noUndo); if (dirty()) { masterScore()->setPlaylistDirty(); // TODO: flag individual operations masterScore()->setAutosaveDirty(true); } MuseScoreCore::mscoreCore->endCmd(); cmdState().reset(); } #ifndef NDEBUG //--------------------------------------------------------- // CmdState::dump //--------------------------------------------------------- void CmdState::dump() { qDebug("CmdState: mode %d %d-%d", int(_updateMode), _startTick.ticks(), _endTick.ticks()); // bool _excerptsChanged { false }; // bool _instrumentsChanged { false }; } #endif //--------------------------------------------------------- // update // layout & update //--------------------------------------------------------- void Score::update(bool resetCmdState) { bool updateAll = false; for (MasterScore* ms : *movements()) { CmdState& cs = ms->cmdState(); ms->deletePostponed(); if (cs.layoutRange()) { for (Score* s : ms->scoreList()) s->doLayoutRange(cs.startTick(), cs.endTick()); updateAll = true; } } for (MasterScore* ms : *movements()) { CmdState& cs = ms->cmdState(); if (updateAll || cs.updateAll()) { for (Score* s : scoreList()) { for (MuseScoreView* v : s->viewer) { v->updateAll(); } } } else if (cs.updateRange()) { // updateRange updates only current score qreal d = spatium() * .5; _updateState.refresh.adjust(-d, -d, 2 * d, 2 * d); for (MuseScoreView* v : viewer) v->dataChanged(_updateState.refresh); _updateState.refresh = QRectF(); } const InputState& is = inputState(); if (is.noteEntryMode() && is.segment()) { setPlayPos(is.segment()->tick()); } if (playlistDirty()) { for (Score* s : scoreList()) emit s->playlistChanged(); masterScore()->setPlaylistClean(); } if (resetCmdState) cs.reset(); } if (_selection.isRange()) _selection.updateSelectedElements(); } //--------------------------------------------------------- // deletePostponed //--------------------------------------------------------- void Score::deletePostponed() { for (ScoreElement* e : _updateState._deleteList) { if (e->isSystem()) { System* s = toSystem(e); for (SpannerSegment* ss : s->spannerSegments()) { if (ss->system() == s) ss->setSystem(0); } } } qDeleteAll(_updateState._deleteList); _updateState._deleteList.clear(); } //--------------------------------------------------------- // cmdAddSpanner // drop VOLTA, OTTAVA, TRILL, PEDAL, DYNAMIC // HAIRPIN, LET_RING, VIBRATO and TEXTLINE //--------------------------------------------------------- void Score::cmdAddSpanner(Spanner* spanner, const QPointF& pos, bool firstStaffOnly) { int staffIdx; Segment* segment; MeasureBase* mb = pos2measure(pos, &staffIdx, 0, &segment, 0); if (firstStaffOnly) staffIdx = 0; // ignore if we do not have a measure if (mb == 0 || mb->type() != ElementType::MEASURE) { qDebug("cmdAddSpanner: cannot put object here"); delete spanner; return; } // all spanners live in voice 0 (except slurs/ties) int track = staffIdx == -1 ? -1 : staffIdx * VOICES; spanner->setTrack(track); spanner->setTrack2(track); if (spanner->anchor() == Spanner::Anchor::SEGMENT) { spanner->setTick(segment->tick()); Fraction lastTick = lastMeasure()->tick() + lastMeasure()->ticks(); Fraction tick2 = qMin(segment->measure()->tick() + segment->measure()->ticks(), lastTick); spanner->setTick2(tick2); } else { // Anchor::MEASURE, Anchor::CHORD, Anchor::NOTE Measure* m = toMeasure(mb); QRectF b(m->canvasBoundingRect()); if (pos.x() >= (b.x() + b.width() * .5) && m != lastMeasureMM() && m->nextMeasure()->system() == m->system()) m = m->nextMeasure(); spanner->setTick(m->tick()); spanner->setTick2(m->endTick()); } spanner->eraseSpannerSegments(); undoAddElement(spanner); select(spanner, SelectType::SINGLE, 0); } //--------------------------------------------------------- // cmdAddSpanner // used when applying a spanner to a selection //--------------------------------------------------------- void Score::cmdAddSpanner(Spanner* spanner, int staffIdx, Segment* startSegment, Segment* endSegment) { int track = staffIdx * VOICES; spanner->setTrack(track); spanner->setTrack2(track); for (auto ss : spanner->spannerSegments()) ss->setTrack(track); spanner->setTick(startSegment->tick()); Fraction tick2; if (!endSegment) tick2 = lastSegment()->tick(); else if (endSegment == startSegment) tick2 = startSegment->measure()->last()->tick(); else tick2 = endSegment->tick(); spanner->setTick2(tick2); #if 0 // TODO TextLine* tl = toTextLine(spanner); if (tl) { StyledPropertyListIdx st; Text* t; // begin t = tl->beginTextElement(); if (t) { st = t->textStyleType(); if (st >= StyledPropertyListIdx::DEFAULT) t->textStyle().restyle(MScore::baseStyle().textStyle(st), textStyle(st)); } // continue t = tl->continueTextElement(); if (t) { st = t->textStyleType(); if (st >= StyledPropertyListIdx::DEFAULT) t->textStyle().restyle(MScore::baseStyle().textStyle(st), textStyle(st)); } // end t = tl->endTextElement(); if (t) { st = t->textStyleType(); if (st >= StyledPropertyListIdx::DEFAULT) t->textStyle().restyle(MScore::baseStyle().textStyle(st), textStyle(st)); } } #endif undoAddElement(spanner); } //--------------------------------------------------------- // expandVoice // fills gaps in voice with rests, // from previous cr (or beginning of measure) to next cr (or end of measure) //--------------------------------------------------------- void Score::expandVoice(Segment* s, int track) { if (!s) { qDebug("expand voice: no segment"); return; } if (s->element(track)) return; // find previous segment with cr in this track Segment* ps; for (ps = s; ps; ps = ps->prev(SegmentType::ChordRest)) { if (ps->element(track)) break; } if (ps) { ChordRest* cr = toChordRest(ps->element(track)); Fraction tick = cr->tick() + cr->actualTicks(); if (tick > s->tick()) { // previous cr extends past current segment qDebug("expandVoice: cannot insert element here"); return; } if (cr->isChord()) { // previous cr ends on or before current segment // for chords, move ps to just after cr ends // so we can fill any gap that might exist // but don't move ps if previous cr is a rest // this will be combined with any new rests needed to fill up to s->tick() below ps = ps->measure()->undoGetSegment(SegmentType::ChordRest, tick); } } // // fill up to s->tick() with rests // Measure* m = s->measure(); Fraction stick = ps ? ps->tick() : m->tick(); Fraction ticks = s->tick() - stick; if (ticks.isNotZero()) setRest(stick, track, ticks, false, 0); // // fill from s->tick() until next chord/rest in measure // Segment* ns; for (ns = s->next(SegmentType::ChordRest); ns; ns = ns->next(SegmentType::ChordRest)) { if (ns->element(track)) break; } ticks = ns ? (ns->tick() - s->tick()) : (m->ticks() - s->rtick()); if (ticks == m->ticks()) addRest(s, track, TDuration(TDuration::DurationType::V_MEASURE), 0); else setRest(s->tick(), track, ticks, false, 0); } void Score::expandVoice() { Segment* s = _is.segment(); int track = _is.track(); expandVoice(s, track); } //--------------------------------------------------------- // cmdAddInterval //--------------------------------------------------------- void Score::cmdAddInterval(int val, const std::vector& nl) { startCmd(); for (Note* on : nl) { Note* note = new Note(this); Chord* chord = on->chord(); note->setParent(chord); note->setTrack(chord->track()); int valTmp = val < 0 ? val+1 : val-1; int npitch; int ntpc1; int ntpc2; if (abs(valTmp) != 7) { int line = on->line() - valTmp; Fraction tick = chord->tick(); Staff* estaff = staff(on->staffIdx() + chord->staffMove()); ClefType clef = estaff->clef(tick); Key key = estaff->key(tick); npitch = line2pitch(line, clef, key); int ntpc = pitch2tpc(npitch, key, Prefer::NEAREST); Interval v = on->part()->instrument(tick)->transpose(); if (v.isZero()) ntpc1 = ntpc2 = ntpc; else { if (styleB(Sid::concertPitch)) { v.flip(); ntpc1 = ntpc; ntpc2 = Ms::transposeTpc(ntpc, v, true); } else { npitch += v.chromatic; ntpc2 = ntpc; ntpc1 = Ms::transposeTpc(ntpc, v, true); } } } else { //special case for octave Interval interval(7, 12); if (val < 0) interval.flip(); transposeInterval(on->pitch(), on->tpc(), &npitch, &ntpc1, interval, false); ntpc1 = on->tpc1(); ntpc2 = on->tpc2(); } if (npitch < 0 || npitch > 127) { delete note; endCmd(); return; } note->setPitch(npitch, ntpc1, ntpc2); undoAddElement(note); setPlayNote(true); select(note, SelectType::SINGLE, 0); } _is.moveToNextInputPos(); endCmd(); } //--------------------------------------------------------- // setGraceNote /// Create a grace note in front of a normal note. /// \arg ch is the chord of the normal note /// \arg pitch is the pitch of the grace note /// \arg is the grace note type /// \len is the visual duration of the grace note (1/16 or 1/32) //--------------------------------------------------------- Note* Score::setGraceNote(Chord* ch, int pitch, NoteType type, int len) { Note* note = new Note(this); Chord* chord = new Chord(this); // allow grace notes to be added to other grace notes // by really adding to parent chord if (ch->noteType() != NoteType::NORMAL) ch = toChord(ch->parent()); chord->setTrack(ch->track()); chord->setParent(ch); chord->add(note); note->setPitch(pitch); // find corresponding note within chord and use its tpc information for (Note* n : ch->notes()) { if (n->pitch() == pitch) { note->setTpc1(n->tpc1()); note->setTpc2(n->tpc2()); break; } } // note with same pitch not found, derive tpc from pitch / key if (!tpcIsValid(note->tpc1()) || !tpcIsValid(note->tpc2())) note->setTpcFromPitch(); TDuration d; d.setVal(len); chord->setDurationType(d); chord->setTicks(d.fraction()); chord->setNoteType(type); chord->setMag(ch->staff()->mag(chord->tick()) * styleD(Sid::graceNoteMag)); undoAddElement(chord); select(note, SelectType::SINGLE, 0); return note; } //--------------------------------------------------------- // createCRSequence // Create a rest or chord of len f. // If f is not a basic len, create several rests or // tied chords. // // f total len of ChordRest // cr prototype CR // tick start position in measure //--------------------------------------------------------- void Score::createCRSequence(const Fraction& f, ChordRest* cr, const Fraction& t) { Fraction tick(t); Measure* measure = cr->measure(); ChordRest* ocr = 0; for (TDuration d : toDurationList(f, true)) { ChordRest* ncr = toChordRest(cr->clone()); ncr->setDurationType(d); ncr->setTicks(d.fraction()); undoAddCR(ncr, measure, measure->tick() + tick); if (cr->isChord() && ocr) { Chord* nc = toChord(ncr); Chord* oc = toChord(ocr); for (unsigned int i = 0; i < oc->notes().size(); ++i) { Note* on = oc->notes()[i]; Note* nn = nc->notes()[i]; Tie* tie = new Tie(this); tie->setStartNote(on); tie->setEndNote(nn); tie->setTrack(cr->track()); on->setTieFor(tie); nn->setTieBack(tie); undoAddElement(tie); } } tick += ncr->actualTicks(); ocr = ncr; } } //--------------------------------------------------------- // setNoteRest // pitch == -1 -> set rest // return segment of last created note/rest //--------------------------------------------------------- Segment* Score::setNoteRest(Segment* segment, int track, NoteVal nval, Fraction sd, Direction stemDirection, bool forceAccidental, bool rhythmic) { Q_ASSERT(segment->segmentType() == SegmentType::ChordRest); bool isRest = nval.pitch == -1; Fraction tick = segment->tick(); Element* nr = 0; Tie* tie = 0; ChordRest* cr = toChordRest(segment->element(track)); Measure* measure = 0; for (;;) { if (track % VOICES) expandVoice(segment, track); // the returned gap ends at the measure boundary or at tuplet end Fraction dd = makeGap(segment, track, sd, cr ? cr->tuplet() : 0); if (dd.isZero()) { qDebug("cannot get gap at %d type: %d/%d", tick.ticks(), sd.numerator(), sd.denominator()); break; } measure = segment->measure(); std::vector dl; if (rhythmic) dl = toRhythmicDurationList(dd, isRest, segment->rtick(), sigmap()->timesig(tick).nominal(), measure, 1); else dl = toDurationList(dd, true); size_t n = dl.size(); for (size_t i = 0; i < n; ++i) { const TDuration& d = dl[i]; ChordRest* ncr; Note* note = 0; Tie* addTie = 0; if (isRest) { nr = ncr = new Rest(this); nr->setTrack(track); ncr->setDurationType(d); ncr->setTicks(d == TDuration::DurationType::V_MEASURE ? measure->ticks() : d.fraction()); } else { nr = note = new Note(this); if (tie) { tie->setEndNote(note); note->setTieBack(tie); addTie = tie; } Chord* chord = new Chord(this); chord->setTrack(track); chord->setDurationType(d); chord->setTicks(d.fraction()); chord->setStemDirection(stemDirection); chord->add(note); note->setNval(nval, tick); if (forceAccidental) { int tpc = styleB(Sid::concertPitch) ? nval.tpc2 : nval.tpc1; AccidentalVal alter = tpc2alter(tpc); AccidentalType at = Accidental::value2subtype(alter); Accidental* a = new Accidental(this); a->setAccidentalType(at); a->setRole(AccidentalRole::USER); note->add(a); } ncr = chord; if (i+1 < n) { tie = new Tie(this); tie->setStartNote(note); tie->setTrack(track); note->setTieFor(tie); } } ncr->setTuplet(cr ? cr->tuplet() : 0); undoAddCR(ncr, measure, tick); if (addTie) undoAddElement(addTie); setPlayNote(true); segment = ncr->segment(); tick += ncr->actualTicks(); } sd -= dd; if (sd.isZero()) break; Segment* nseg = tick2segment(tick, false, SegmentType::ChordRest); if (nseg == 0) { qDebug("reached end of score"); break; } segment = nseg; cr = toChordRest(segment->element(track)); if (cr == 0) { if (track % VOICES) cr = addRest(segment, track, TDuration(TDuration::DurationType::V_MEASURE), 0); else { qDebug("no rest in voice 0"); break; } } // // Note does not fit on current measure, create Tie to // next part of note if (!isRest) { tie = new Tie(this); tie->setStartNote((Note*)nr); tie->setTrack(nr->track()); ((Note*)nr)->setTieFor(tie); } } if (tie) connectTies(); if (nr) { if (_is.slur() && nr->type() == ElementType::NOTE) { // If the start element was the same as the end element when the slur was created, // the end grip of the front slur segment was given an x-offset of 3.0 * spatium(). // Now that the slur is about to be given a new end element, this should be reset. if (_is.slur()->endElement() == _is.slur()->startElement()) _is.slur()->frontSegment()->reset(); // // extend slur // Chord* chord = toNote(nr)->chord(); _is.slur()->undoChangeProperty(Pid::SPANNER_TICKS, chord->tick() - _is.slur()->tick()); for (ScoreElement* se : _is.slur()->linkList()) { Slur* slur = toSlur(se); for (ScoreElement* ee : chord->linkList()) { Element* e = static_cast(ee); if (e->score() == slur->score() && e->track() == slur->track2()) { slur->score()->undo(new ChangeSpannerElements(slur, slur->startElement(), e)); break; } } } } select(nr, SelectType::SINGLE, 0); } return segment; } //--------------------------------------------------------- // makeGap // make time gap at tick by removing/shortening // chord/rest // // if keepChord, the chord at tick is not removed // // gap does not exceed measure or scope of tuplet // // return size of actual gap //--------------------------------------------------------- Fraction Score::makeGap(Segment* segment, int track, const Fraction& _sd, Tuplet* tuplet, bool keepChord) { Q_ASSERT(_sd.numerator()); Measure* measure = segment->measure(); Fraction akkumulated; Fraction sd = _sd; // // remember first segment which should // not be deleted (it may contain other elements we want to preserve) // Segment* firstSegment = segment; const Fraction firstSegmentEnd = firstSegment->tick() + firstSegment->ticks(); Fraction nextTick = segment->tick(); for (Segment* seg = firstSegment; seg; seg = seg->next(SegmentType::ChordRest)) { // // voices != 0 may have gaps: // ChordRest* cr = toChordRest(seg->element(track)); if (!cr) { if (seg->tick() < nextTick) continue; Segment* seg1 = seg->next(SegmentType::ChordRest); Fraction tick2 = seg1 ? seg1->tick() : seg->measure()->tick() + seg->measure()->ticks(); segment = seg; Fraction td(tick2 - seg->tick()); if (td > sd) td = sd; akkumulated += td; sd -= td; if (sd.isZero()) break; nextTick = tick2; continue; } if (seg->tick() > nextTick) { // there was a gap Fraction td(seg->tick() - nextTick); if (td > sd) td = sd; akkumulated += td; sd -= td; if (sd.isZero()) break; } // // limit to tuplet level // if (tuplet) { bool tupletEnd = true; Tuplet* t = cr->tuplet(); while (t) { if (cr->tuplet() == tuplet) { tupletEnd = false; break; } t = t->tuplet(); } if (tupletEnd) break; } Fraction td(cr->ticks()); // remove tremolo between 2 notes, if present if (cr->isChord()) { Chord* c = toChord(cr); if (c->tremolo()) { Tremolo* tremolo = c->tremolo(); if (tremolo->twoNotes()) undoRemoveElement(tremolo); } } Tuplet* ltuplet = cr->tuplet(); if (ltuplet != tuplet) { // // Current location points to the start of a (nested)tuplet. // We have to remove the complete tuplet. // get top level tuplet while (ltuplet->tuplet()) ltuplet = ltuplet->tuplet(); // get last segment of tuplet, drilling down to leaf nodes as necessary Tuplet* t = ltuplet; while (t->elements().back()->isTuplet()) t = toTuplet(t->elements().back()); seg = toChordRest(t->elements().back())->segment(); // now delete the full tuplet td = ltuplet->ticks(); cmdDeleteTuplet(ltuplet, false); tuplet = 0; } else { if (seg != firstSegment || !keepChord) undoRemoveElement(cr); // even if there was a tuplet, we didn't remove it ltuplet = 0; } nextTick += (tuplet ? td / tuplet->ratio() : td); if (sd < td) { // // we removed too much // akkumulated = _sd; Fraction rd = td - sd; std::vector dList = toDurationList(rd, false); if (dList.empty()) break; Fraction f = sd / cr->staff()->timeStretch(cr->tick()); for (Tuplet* t = tuplet; t; t = t->tuplet()) f /= t->ratio(); Fraction tick = cr->tick() + f; if ((tuplet == 0) && (((measure->tick() - tick).ticks() % dList[0].ticks().ticks()) == 0)) { for (TDuration d : dList) { if (ltuplet) { // take care not to recreate tuplet we just deleted Rest* r = setRest(tick, track, d.fraction(), false, 0, false); tick += r->actualTicks(); } else { tick += addClone(cr, tick, d)->actualTicks(); } } } else { for (int i = int(dList.size()) - 1; i >= 0; --i) { if (ltuplet) { // take care not to recreate tuplet we just deleted Rest* r = setRest(tick, track, dList[i].fraction(), false, 0, false); tick += r->actualTicks(); } else { tick += addClone(cr, tick, dList[i])->actualTicks(); } } } break; } akkumulated += td; sd -= td; if (sd.isZero()) break; } // Fraction ticks = measure->tick() + measure->ticks() - segment->tick(); // Fraction td = Fraction::fromTicks(ticks); // NEEDS REVIEW !! // once the statement below is removed, these two lines do nothing // if (td > sd) // td = sd; // ??? akkumulated should already contain the total value of the created gap: line 749, 811 or 838 // this line creates a qreal-sized gap if the needed gap crosses a measure boundary // by adding again the duration already added in line 838 // akkumulated += td; const Fraction t1 = firstSegmentEnd; const Fraction t2 = firstSegment->tick() + akkumulated; if (t1 < t2) { Segment* s1 = tick2rightSegment(t1); Segment* s2 = tick2rightSegment(t2); typedef SelectionFilterType Sel; // chord symbols can exist without chord/rest so they should not be removed constexpr Sel filter = static_cast(int(Sel::ALL) & ~int(Sel::CHORD_SYMBOL)); deleteAnnotationsFromRange(s1, s2, track, track + 1, filter); deleteSpannersFromRange(t1, t2, track, track + 1, filter); } return akkumulated; } //--------------------------------------------------------- // makeGap1 // make time gap for each voice // starting at tick+voiceOffset[voice] by removing/shortening // chord/rest // - cr is top level (not part of a tuplet) // - do not stop at measure end //--------------------------------------------------------- bool Score::makeGap1(const Fraction& baseTick, int staffIdx, const Fraction& len, int voiceOffset[VOICES]) { Segment* seg = tick2segment(baseTick, true, SegmentType::ChordRest); if (!seg) { qDebug("no segment to paste at tick %d", baseTick.ticks()); return false; } int strack = staffIdx * VOICES; for (int track = strack; track < strack + VOICES; track++) { if (voiceOffset[track-strack] == -1) continue; Fraction tick = baseTick + Fraction::fromTicks(voiceOffset[track-strack]); Measure* m = tick2measure(tick); if ((track % VOICES) && !m->hasVoices(staffIdx)) continue; Fraction newLen = len - Fraction::fromTicks(voiceOffset[track-strack]); Q_ASSERT(newLen.numerator() != 0); if (newLen > Fraction(0,1)) { const Fraction endTick = tick + newLen; deleteAnnotationsFromRange(tick2rightSegment(tick), tick2rightSegment(endTick), track, track + 1, selectionFilter()); deleteSpannersFromRange(tick, endTick, track, track + 1, selectionFilter()); } seg = m->undoGetSegment(SegmentType::ChordRest, tick); bool result = makeGapVoice(seg, track, newLen, tick); if (track == strack && !result) // makeGap failed for first voice return false; } return true; } bool Score::makeGapVoice(Segment* seg, int track, Fraction len, const Fraction& tick) { ChordRest* cr = 0; cr = toChordRest(seg->element(track)); if (!cr) { // check if we are in the middle of a chord/rest Segment* seg1 = seg->prev(SegmentType::ChordRest); for (;;) { if (seg1 == 0) { if (!(track % VOICES)) qDebug("no segment before tick %d", tick.ticks()); // this happens only for voices other than voice 1 expandVoice(seg, track); return makeGapVoice(seg,track,len,tick); } if (seg1->element(track)) break; seg1 = seg1->prev(SegmentType::ChordRest); } ChordRest* cr1 = toChordRest(seg1->element(track)); Fraction srcF = cr1->ticks(); Fraction dstF = tick - cr1->tick(); std::vector dList = toDurationList(dstF, true); size_t n = dList.size(); undoChangeChordRestLen(cr1, TDuration(dList[0])); if (n > 1) { Fraction crtick = cr1->tick() + cr1->actualTicks(); Measure* measure = tick2measure(crtick); if (cr1->type() == ElementType::CHORD) { // split Chord Chord* c = toChord(cr1); for (size_t i = 1; i < n; ++i) { TDuration d = dList[i]; Chord* c2 = addChord(crtick, d, c, true, c->tuplet()); c = c2; seg1 = c->segment(); crtick += c->actualTicks(); } } else { // split Rest Rest* r = toRest(cr1); for (size_t i = 1; i < n; ++i) { TDuration d = dList[i]; Rest* r2 = toRest(r->clone()); r2->setTicks(d.fraction()); r2->setDurationType(d); undoAddCR(r2, measure, crtick); seg1 = r2->segment(); crtick += r2->actualTicks(); } } } setRest(tick, track, srcF - dstF, true, 0); for (;;) { seg1 = seg1->next1(SegmentType::ChordRest); if (seg1 == 0) { qDebug("no segment"); return false; } if (seg1->element(track)) { cr = toChordRest(seg1->element(track)); break; } } } for (;;) { if (!cr) { qDebug("cannot make gap"); return false; } Fraction l = makeGap(cr->segment(), cr->track(), len, 0); if (l.isZero()) { qDebug("returns zero gap"); return false; } len -= l; if (len.isZero()) break; // go to next cr Measure* m = cr->measure()->nextMeasure(); if (m == 0) { qDebug("EOS reached"); insertMeasure(ElementType::MEASURE, 0, false); m = cr->measure()->nextMeasure(); if (m == 0) { qDebug("===EOS reached"); return true; } } // first segment in measure was removed, have to recreate it Segment* s = m->undoGetSegment(SegmentType::ChordRest, m->tick()); int t = cr->track(); cr = toChordRest(s->element(t)); if (cr == 0) { addRest(s, t, TDuration(TDuration::DurationType::V_MEASURE), 0); cr = toChordRest(s->element(t)); } } return true; } //--------------------------------------------------------- // splitGapToMeasureBoundaries // cr - start of gap // gap - gap len //--------------------------------------------------------- QList Score::splitGapToMeasureBoundaries(ChordRest* cr, Fraction gap) { QList flist; Tuplet* tuplet = cr->tuplet(); if (tuplet) { if (tuplet->tuplet()) return flist; // do no deal with nested tuplets Fraction rest = tuplet->elementsDuration(); for (DurationElement* de : tuplet->elements()) { if (de == cr) break; rest -= de->ticks(); } if (rest < gap) qDebug("does not fit in tuplet"); else flist.append(gap); return flist; } Segment* s = cr->segment(); while (gap > Fraction(0,1)) { Measure* m = s->measure(); Fraction timeStretch = cr->staff()->timeStretch(s->tick()); Fraction rest = (m->ticks() - s->rtick()) * timeStretch; if (rest >= gap) { flist.append(gap); return flist; } flist.append(rest); gap -= rest; m = m->nextMeasure(); if (m == 0) return flist; s = m->first(SegmentType::ChordRest); } return flist; } //--------------------------------------------------------- // changeCRlen //--------------------------------------------------------- void Score::changeCRlen(ChordRest* cr, const TDuration& d) { Fraction dstF; if (d.type() == TDuration::DurationType::V_MEASURE) dstF = cr->measure()->stretchedLen(cr->staff()); else dstF = d.fraction(); changeCRlen(cr, dstF); } void Score::changeCRlen(ChordRest* cr, const Fraction& dstF, bool fillWithRest) { if (cr->isRepeatMeasure()) { // it is not clear what should this // operation mean for measure repeats. return; } Fraction srcF(cr->ticks()); if (srcF == dstF) return; //keep selected element if any Element* selElement = selection().isSingle() ? getSelectedElement() : 0; int track = cr->track(); Tuplet* tuplet = cr->tuplet(); if (srcF > dstF) { // // make shorter and fill with rest // deselectAll(); if (cr->isChord()) { // // remove ties and tremolo between 2 notes // Chord* c = toChord(cr); if (c->tremolo()) { Tremolo* tremolo = c->tremolo(); if (tremolo->twoNotes()) undoRemoveElement(tremolo); } foreach (Note* n, c->notes()) { if (n->tieFor()) undoRemoveElement(n->tieFor()); } } std::vector dList = toDurationList(dstF, true); undoChangeChordRestLen(cr, dList[0]); Fraction tick2 = cr->tick(); for (unsigned i = 1; i < dList.size(); ++i) { tick2 += dList[i-1].ticks(); TDuration d = dList[i]; setRest(tick2, track, d.fraction(), (d.dots() > 0), tuplet); } if (fillWithRest) setRest(cr->tick() + cr->actualTicks(), track, srcF - dstF, false, tuplet); if (selElement) select(selElement, SelectType::SINGLE, 0); return; } // // make longer // // split required len into Measures QList flist = splitGapToMeasureBoundaries(cr, dstF); if (flist.empty()) return; deselectAll(); Fraction tick = cr->tick(); Fraction f = dstF; ChordRest* cr1 = cr; Chord* oc = 0; bool first = true; for (Fraction f2 : flist) { f -= f2; makeGap(cr1->segment(), cr1->track(), f2, tuplet, first); if (cr->isRest()) { Fraction timeStretch = cr1->staff()->timeStretch(cr1->tick()); Rest* r = toRest(cr); if (first) { std::vector dList = toDurationList(f2, true); undoChangeChordRestLen(cr, dList[0]); Fraction tick2 = cr->tick(); for (unsigned i = 1; i < dList.size(); ++i) { tick2 += dList[i-1].ticks(); TDuration d = dList[i]; setRest(tick2, track, d.fraction() * timeStretch, (d.dots() > 0), tuplet); } } else { r = setRest(tick, track, f2 * timeStretch, false, tuplet); } if (first) { select(r, SelectType::SINGLE, 0); first = false; } tick += f2 * timeStretch; } else { std::vector dList = toDurationList(f2, true); Measure* measure = tick2measure(tick); Fraction etick = measure->tick(); if (((tick - etick).ticks() % dList[0].ticks().ticks()) == 0) { for (TDuration du : dList) { bool genTie; Chord* cc; if (oc) { genTie = true; cc = oc; oc = addChord(tick, du, cc, genTie, tuplet); } else { genTie = false; cc = toChord(cr); undoChangeChordRestLen(cr, du); oc = cc; } if (oc && first) { if (!selElement) select(oc, SelectType::SINGLE, 0); else select(selElement, SelectType::SINGLE, 0); first = false; } if (oc) tick += oc->actualTicks(); } } else { for (int i = int(dList.size()) - 1; i >= 0; --i) { bool genTie; Chord* cc; if (oc) { genTie = true; cc = oc; oc = addChord(tick, dList[i], cc, genTie, tuplet); } else { genTie = false; cc = toChord(cr); undoChangeChordRestLen(cr, dList[i]); oc = cc; } if (first) { // select(oc, SelectType::SINGLE, 0); if (selElement) select(selElement, SelectType::SINGLE, 0); first = false; } tick += oc->actualTicks(); } } } Measure* m = cr1->measure(); Measure* m1 = m->nextMeasure(); if (m1 == 0) break; Segment* s = m1->first(SegmentType::ChordRest); expandVoice(s, track); cr1 = toChordRest(s->element(track)); } connectTies(); } //--------------------------------------------------------- // upDownChromatic //--------------------------------------------------------- static void upDownChromatic(bool up, int pitch, Note* n, Key key, int tpc1, int tpc2, int& newPitch, int& newTpc1, int& newTpc2) { if (up && pitch < 127) { newPitch = pitch + 1; if (n->concertPitch()) { if (tpc1 > Tpc::TPC_A + int(key)) newTpc1 = tpc1 - 5; // up semitone diatonic else newTpc1 = tpc1 + 7; // up semitone chromatic newTpc2 = n->transposeTpc(newTpc1); } else { if (tpc2 > Tpc::TPC_A + int(key)) newTpc2 = tpc2 - 5; // up semitone diatonic else newTpc2 = tpc2 + 7; // up semitone chromatic newTpc1 = n->transposeTpc(newTpc2); } } else if (!up && pitch > 0) { newPitch = pitch - 1; if (n->concertPitch()) { if (tpc1 > Tpc::TPC_C + int(key)) newTpc1 = tpc1 - 7; // down semitone chromatic else newTpc1 = tpc1 + 5; // down semitone diatonic newTpc2 = n->transposeTpc(newTpc1); } else { if (tpc2 > Tpc::TPC_C + int(key)) newTpc2 = tpc2 - 7; // down semitone chromatic else newTpc2 = tpc2 + 5; // down semitone diatonic newTpc1 = n->transposeTpc(newTpc2); } } } //--------------------------------------------------------- // setTpc //--------------------------------------------------------- static void setTpc(Note* oNote, int tpc, int& newTpc1, int& newTpc2) { if (oNote->concertPitch()) { newTpc1 = tpc; newTpc2 = oNote->transposeTpc(tpc); } else { newTpc2 = tpc; newTpc1 = oNote->transposeTpc(tpc); } } //--------------------------------------------------------- // upDown /// Increment/decrement pitch of note by one or by an octave. //--------------------------------------------------------- void Score::upDown(bool up, UpDownMode mode) { QList el = selection().uniqueNotes(); for (Note* oNote : el) { Fraction tick = oNote->chord()->tick(); Staff* staff = oNote->staff(); Part* part = staff->part(); Key key = staff->key(tick); int tpc1 = oNote->tpc1(); int tpc2 = oNote->tpc2(); int pitch = oNote->pitch(); int newTpc1 = tpc1; // default to unchanged int newTpc2 = tpc2; // default to unchanged int newPitch = pitch; // default to unchanged int string = oNote->string(); int fret = oNote->fret(); switch (staff->staffType(oNote->chord()->tick())->group()) { case StaffGroup::PERCUSSION: { const Drumset* ds = part->instrument()->drumset(); if (ds) { newPitch = up ? ds->nextPitch(pitch) : ds->prevPitch(pitch); newTpc1 = pitch2tpc(newPitch, Key::C, Prefer::NEAREST); newTpc2 = newTpc1; } } break; case StaffGroup::TAB: { const StringData* stringData = part->instrument()->stringData(); switch (mode) { case UpDownMode::OCTAVE: // move same note to next string, if possible { const StaffType* stt = staff->staffType(tick); string = stt->physStringToVisual(string); string += (up ? -1 : 1); if (string < 0 || string >= stringData->strings()) return; // no next string to move to string = stt->visualStringToPhys(string); fret = stringData->fret(pitch, string, staff, tick); if (fret == -1) // can't have that note on that string return; // newPitch and newTpc remain unchanged } break; case UpDownMode::DIATONIC: // increase / decrease the pitch, // letting the algorithm to choose fret & string upDownChromatic(up, pitch, oNote, key, tpc1, tpc2, newPitch, newTpc1, newTpc2); break; case UpDownMode::CHROMATIC: // increase / decrease the fret { // without changing the string // compute new fret if (!stringData->frets()) { qDebug("upDown tab chromatic: no frets?"); return; } fret += (up ? 1 : -1); if (fret < 0 || fret > stringData->frets()) { qDebug("upDown tab in-string: out of fret range"); return; } // update pitch and tpc's and check it matches stringData upDownChromatic(up, pitch, oNote, key, tpc1, tpc2, newPitch, newTpc1, newTpc2); if (newPitch != stringData->getPitch(string, fret, staff, tick) ) { // oh-oh: something went very wrong! qDebug("upDown tab in-string: pitch mismatch"); return; } // store the fretting change before undoChangePitch() chooses // a fretting of its own liking! oNote->undoChangeProperty(Pid::FRET, fret); } break; } } break; case StaffGroup::STANDARD: switch (mode) { case UpDownMode::OCTAVE: if (up) { if (pitch < 116) newPitch = pitch + 12; } else { if (pitch > 11) newPitch = pitch - 12; } // newTpc remains unchanged break; case UpDownMode::CHROMATIC: upDownChromatic(up, pitch, oNote, key, tpc1, tpc2, newPitch, newTpc1, newTpc2); break; case UpDownMode::DIATONIC: { int tpc = oNote->tpc(); if (up) { if (tpc > Tpc::TPC_A + int(key)) { if (pitch < 127) { newPitch = pitch + 1; setTpc(oNote, tpc - 5, newTpc1, newTpc2); } } else { if (pitch < 126) { newPitch = pitch + 2; setTpc(oNote, tpc + 2, newTpc1, newTpc2); } } } else { if (tpc > Tpc::TPC_C + int(key)) { if (pitch > 1) { newPitch = pitch - 2; setTpc(oNote, tpc - 2, newTpc1, newTpc2); } } else { if (pitch > 0) { newPitch = pitch - 1; setTpc(oNote, tpc + 5, newTpc1, newTpc2); } } } } break; } break; } if ((oNote->pitch() != newPitch) || (oNote->tpc1() != newTpc1) || oNote->tpc2() != newTpc2) { // remove accidental if present to make sure // user added accidentals are removed here. auto l = oNote->linkList(); for (ScoreElement* e : l) { Note* ln = toNote(e); if (ln->accidental()) undo(new RemoveElement(ln->accidental())); } undoChangePitch(oNote, newPitch, newTpc1, newTpc2); } // store fret change only if undoChangePitch has not been called, // as undoChangePitch() already manages fret changes, if necessary else if (staff->staffType(tick)->group() == StaffGroup::TAB) { bool refret = false; if (oNote->string() != string) { oNote->undoChangeProperty(Pid::STRING, string); refret = true; } if (oNote->fret() != fret) { oNote->undoChangeProperty(Pid::FRET, fret); refret = true; } if (refret) { const StringData* stringData = part->instrument()->stringData(); stringData->fretChords(oNote->chord()); } } // play new note with velocity 80 for 0.3 sec: setPlayNote(true); } setSelectionChanged(true); } //--------------------------------------------------------- // upDownDelta /// Add the delta to the pitch of note. //--------------------------------------------------------- void Score::upDownDelta(int pitchDelta) { while (pitchDelta > 0) { upDown(true, UpDownMode::CHROMATIC); pitchDelta--; } while (pitchDelta < 0) { upDown(false, UpDownMode::CHROMATIC); pitchDelta++; } } //--------------------------------------------------------- // addArticulation /// Add attribute \a attr to all selected notes/rests. /// /// Called from padToggle() to add note prefix/accent. //--------------------------------------------------------- void Score::addArticulation(SymId attr) { QSet set; for (Element* el : selection().elements()) { if (el->isNote() || el->isChord()) { Chord* cr = 0; // apply articulation on a given chord only once if (el->isNote()) { cr = toNote(el)->chord(); if (set.contains(cr)) continue; } Articulation* na = new Articulation(this); na->setSymId(attr); if (!addArticulation(el, na)) delete na; if (cr) set.insert(cr); } } } //--------------------------------------------------------- // toggleAccidental //--------------------------------------------------------- void Score::toggleAccidental(AccidentalType at, const EditData& ed) { if (_is.accidentalType() == at) at = AccidentalType::NONE; if (noteEntryMode()) { _is.setAccidentalType(at); _is.setRest(false); } else { if (selection().isNone()) { ed.view->startNoteEntryMode(); _is.setAccidentalType(at); _is.setDuration(TDuration::DurationType::V_QUARTER); _is.setRest(false); } else changeAccidental(at); } } //--------------------------------------------------------- // changeAccidental /// Change accidental to subtype \a idx for all selected /// notes. //--------------------------------------------------------- void Score::changeAccidental(AccidentalType idx) { foreach(Note* note, selection().noteList()) changeAccidental(note, idx); } //--------------------------------------------------------- // changeAccidental2 //--------------------------------------------------------- static void changeAccidental2(Note* n, int pitch, int tpc) { Score* score = n->score(); Chord* chord = n->chord(); Staff* st = chord->staff(); int fret = n->fret(); int string = n->string(); if (st->isTabStaff(chord->tick())) { if (pitch != n->pitch()) { // // as pitch has changed, calculate new // string & fret // const StringData* stringData = n->part()->instrument()->stringData(); if (stringData) stringData->convertPitch(pitch, st, chord->tick(), &string, &fret); } } int tpc1; int tpc2 = n->transposeTpc(tpc); if (score->styleB(Sid::concertPitch)) tpc1 = tpc; else { tpc1 = tpc2; tpc2 = tpc; } if (!st->isTabStaff(chord->tick())) { // // handle ties // if (n->tieBack()) { if (pitch != n->pitch()) { score->undoRemoveElement(n->tieBack()); if (n->tieFor()) score->undoRemoveElement(n->tieFor()); } } else { Note* nn = n; while (nn->tieFor()) { nn = nn->tieFor()->endNote(); score->undo(new ChangePitch(nn, pitch, tpc1, tpc2)); } } } score->undoChangePitch(n, pitch, tpc1, tpc2); } //--------------------------------------------------------- // changeAccidental /// Change accidental to subtype \accidental for /// note \a note. //--------------------------------------------------------- void Score::changeAccidental(Note* note, AccidentalType accidental) { Chord* chord = note->chord(); if (!chord) return; Segment* segment = chord->segment(); if (!segment) return; Measure* measure = segment->measure(); if (!measure) return; Fraction tick = segment->tick(); Staff* estaff = staff(chord->staffIdx() + chord->staffMove()); if (!estaff) return; ClefType clef = estaff->clef(tick); int step = ClefInfo::pitchOffset(clef) - note->line(); while (step < 0) step += 7; step %= 7; // // accidental change may result in pitch change // AccidentalVal acc2 = measure->findAccidental(note); AccidentalVal acc = (accidental == AccidentalType::NONE) ? acc2 : Accidental::subtype2value(accidental); int pitch = line2pitch(note->line(), clef, Key::C) + int(acc); if (!note->concertPitch()) pitch += note->transposition(); int tpc = step2tpc(step, acc); bool forceRemove = false; bool forceAdd = false; // delete accidental // both for this note and for any linked notes if (accidental == AccidentalType::NONE) forceRemove = true; // precautionary or microtonal accidental // either way, we display it unconditionally // both for this note and for any linked notes else if (acc == acc2 || pitch == note->pitch() || Accidental::isMicrotonal(accidental)) forceAdd = true; for (ScoreElement* se : note->linkList()) { Note* ln = toNote(se); if (ln->concertPitch() != note->concertPitch()) continue; Score* lns = ln->score(); Accidental* a = ln->accidental(); if (forceRemove) { if (a) lns->undoRemoveElement(a); if (ln->tieBack()) continue; } else if (forceAdd) { if (a) undoRemoveElement(a); Accidental* a1 = new Accidental(lns); a1->setParent(ln); a1->setAccidentalType(accidental); a1->setRole(AccidentalRole::USER); lns->undoAddElement(a1); } changeAccidental2(ln, pitch, tpc); } setPlayNote(true); setSelectionChanged(true); } //--------------------------------------------------------- // addArticulation //--------------------------------------------------------- bool Score::addArticulation(Element* el, Articulation* a) { Chord* c; if (el->isNote()) c = toNote(el)->chord(); else if (el->isChord()) c = toChord(el); else return false; Articulation* oa = c->hasArticulation(a); if (oa) { undoRemoveElement(oa); return false; } a->setParent(c); a->setTrack(c->track()); // make sure it propagates between score and parts undoAddElement(a); return true; } //--------------------------------------------------------- // resetUserStretch //--------------------------------------------------------- void Score::resetUserStretch() { Measure* m1; Measure* m2; // retrieve span of selection Segment* s1 = _selection.startSegment(); Segment* s2 = _selection.endSegment(); // if either segment is not returned by the selection // (for instance, no selection) fall back to first/last measure if (!s1) m1 = firstMeasureMM(); else m1 = s1->measure(); if (!s2) m2 = lastMeasureMM(); else m2 = s2->measure(); if (!m1 || !m2) // should not happen! return; for (Measure* m = m1; m; m = m->nextMeasureMM()) { m->undoChangeProperty(Pid::USER_STRETCH, 1.0); if (m == m2) break; } } //--------------------------------------------------------- // moveUp //--------------------------------------------------------- void Score::moveUp(ChordRest* cr) { Staff* staff = cr->staff(); Part* part = staff->part(); int rstaff = staff->rstaff(); int staffMove = cr->staffMove(); if ((staffMove == -1) || (rstaff + staffMove <= 0)) return; QList* staves = part->staves(); // we know that staffMove+rstaff-1 index exists due to the previous condition. if (staff->staffType(cr->tick())->group() != StaffGroup::STANDARD || staves->at(rstaff+staffMove-1)->staffType(cr->tick())->group() != StaffGroup::STANDARD) { qDebug("User attempted to move a note from/to a staff which does not use standard notation - ignoring."); } else { // move the chord up a staff undo(new ChangeChordStaffMove(cr, staffMove - 1)); } } //--------------------------------------------------------- // moveDown //--------------------------------------------------------- void Score::moveDown(ChordRest* cr) { Staff* staff = cr->staff(); Part* part = staff->part(); int rstaff = staff->rstaff(); int staffMove = cr->staffMove(); // calculate the number of staves available so that we know whether there is another staff to move down to int rstaves = part->nstaves(); if ((staffMove == 1) || (rstaff + staffMove >= rstaves - 1)) { qDebug("moveDown staffMove==%d rstaff %d rstaves %d", staffMove, rstaff, rstaves); return; } QList* staves = part->staves(); // we know that staffMove+rstaff+1 index exists due to the previous condition. if (staff->staffType(cr->tick())->group() != StaffGroup::STANDARD || staves->at(staffMove+rstaff+1)->staffType(cr->tick())->group() != StaffGroup::STANDARD) { qDebug("User attempted to move a note from/to a staff which does not use standard notation - ignoring."); } else { // move the chord down a staff undo(new ChangeChordStaffMove(cr, staffMove + 1)); } } //--------------------------------------------------------- // cmdAddStretch //--------------------------------------------------------- void Score::cmdAddStretch(qreal val) { if (!selection().isRange()) return; Fraction startTick = selection().tickStart(); Fraction endTick = selection().tickEnd(); for (Measure* m = firstMeasureMM(); m; m = m->nextMeasureMM()) { if (m->tick() < startTick) continue; if (m->tick() >= endTick) break; qreal stretch = m->userStretch(); stretch += val; if (stretch < 0) stretch = 0; m->undoChangeProperty(Pid::USER_STRETCH, stretch); } } //--------------------------------------------------------- // cmdResetBeamMode //--------------------------------------------------------- void Score::cmdResetBeamMode() { bool noSelection = selection().isNone(); if (noSelection) cmdSelectAll(); else if (!selection().isRange()) { qDebug("no system or staff selected"); return; } Fraction endTick = selection().tickEnd(); for (Segment* seg = selection().firstChordRestSegment(); seg && seg->tick() < endTick; seg = seg->next1(SegmentType::ChordRest)) { for (int track = selection().staffStart() * VOICES; track < selection().staffEnd() * VOICES; ++track) { ChordRest* cr = toChordRest(seg->element(track)); if (cr == 0) continue; if (cr->type() == ElementType::CHORD) { if (cr->beamMode() != Beam::Mode::AUTO) cr->undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::AUTO)); } else if (cr->type() == ElementType::REST) { if (cr->beamMode() != Beam::Mode::NONE) cr->undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::NONE)); } } } if (noSelection) deselectAll(); } //--------------------------------------------------------- // cmdResetStyle //--------------------------------------------------------- void Score::cmdResetStyle() { style().reset(this); } //--------------------------------------------------------- // cmdResetNoteAndRestGroupings //--------------------------------------------------------- void Score::cmdResetNoteAndRestGroupings() { bool noSelection = selection().isNone(); if (noSelection) cmdSelectAll(); else if (!selection().isRange()) { qDebug("no system or staff selected"); return; } // save selection values because selection changes during grouping Fraction sTick = selection().tickStart(); Fraction eTick = selection().tickEnd(); int sStaff = selection().staffStart(); int eStaff = selection().staffEnd(); startCmd(); for (int staff = sStaff; staff < eStaff; staff++) { int sTrack = staff * VOICES; int eTrack = sTrack + VOICES; for (int track = sTrack; track < eTrack; track++) { if (selectionFilter().canSelectVoice(track)) regroupNotesAndRests(sTick, eTick, track); } } endCmd(); if (noSelection) deselectAll(); } //--------------------------------------------------------- // resetElementShapePosition // For use with Score::scanElements. // Reset positions and autoplacement for the given // element. //--------------------------------------------------------- static void resetElementPosition(void*, Element* e) { if (e->generated()) return; e->undoResetProperty(Pid::AUTOPLACE); e->undoResetProperty(Pid::OFFSET); e->setOffsetChanged(false); if (e->isSpanner()) e->undoResetProperty(Pid::OFFSET2); } //--------------------------------------------------------- // cmdResetAllPositions //--------------------------------------------------------- void Score::cmdResetAllPositions() { startCmd(); scanElements(nullptr, resetElementPosition); endCmd(); } //--------------------------------------------------------- // processMidiInput //--------------------------------------------------------- bool Score::processMidiInput() { if (midiInputQueue()->empty()) return false; if (MScore::debugMode) qDebug("processMidiInput"); NoteEntryMethod entryMethod = _is.noteEntryMethod(); bool cmdActive = false; while (!midiInputQueue()->empty()) { MidiInputEvent ev = midiInputQueue()->dequeue(); for (auto itr = activeMidiPitches()->begin(); itr != activeMidiPitches()->end();) { if ((*itr).pitch == ev.pitch) itr = activeMidiPitches()->erase(itr); else ++itr; } if (MScore::debugMode) qDebug("<-- !noteentry dequeue %i", ev.pitch); if (!noteEntryMode() || entryMethod == NoteEntryMethod::REALTIME_AUTO || entryMethod == NoteEntryMethod::REALTIME_MANUAL) { int staffIdx = selection().staffStart(); Part* p; if (staffIdx < 0 || staffIdx >= nstaves()) p = staff(0)->part(); else p = staff(staffIdx)->part(); if (p) { if (!styleB(Sid::concertPitch)) { ev.pitch += p->instrument(selection().tickStart())->transpose().chromatic; } MScore::seq->startNote( p->instrument()->channel(0)->channel(), ev.pitch, ev.velocity, 0.0); } } if (noteEntryMode()) { if (ev.velocity == 0) { // delete note in realtime mode //Chord* chord = toChord(_is.cr()); //std::vector notes = chord->notes(); if (entryMethod == NoteEntryMethod::REALTIME_AUTO || entryMethod == NoteEntryMethod::REALTIME_MANUAL) { if (_is.cr()->isChord()) { Note* n = toChord(_is.cr())->findNote(ev.pitch); if (n) { qDebug("Pitches match! Note %i, Pitch %i", n->pitch(), ev.pitch); if (!cmdActive) { startCmd(); cmdActive = true; } deleteItem(n->tieBack()); deleteItem(n); } } } continue; } if (!cmdActive) { startCmd(); cmdActive = true; } if (activeMidiPitches()->empty()) ev.chord = false; else ev.chord = true; // holding shift while inputting midi will add the new pitch to the prior existing chord if (qApp->keyboardModifiers() & Qt::ShiftModifier) { Element* cr = _is.lastSegment()->element(_is.track()); if (cr && cr->isChord()) ev.chord = true; } // TODO: add shadow note instead of real note in realtime modes // (note becomes real when realtime-advance triggered). addMidiPitch(ev.pitch, ev.chord); activeMidiPitches()->push_back(ev); } } if (cmdActive) { endCmd(); //after relayout Element* e = inputState().cr(); if (e) { for(MuseScoreView* v : viewer) v->adjustCanvasPosition(e, false); } return true; } return false; } //--------------------------------------------------------- // move // move current selection //--------------------------------------------------------- Element* Score::move(const QString& cmd) { ChordRest* cr; if (noteEntryMode()) { // if selection exists and is grace note, use it // otherwise use chord/rest at input position // also use it if we are moving to next chord // to catch up with the cursor and not move the selection by 2 positions cr = selection().cr(); if (cr && (cr->isGrace() || cmd == "next-chord" || cmd == "prev-chord")) ; else cr = inputState().cr(); } else if (selection().activeCR()) cr = selection().activeCR(); else cr = selection().lastChordRest(); // no chord/rest found? look for another type of element if (cr == 0) { if (selection().elements().empty()) return 0; // retrieve last element of section list Element* el = selection().elements().last(); Element* trg = 0; // get parent of element and process accordingly: // trg is the element to select on "next-chord" cmd // cr is the ChordRest to move from on other cmd's int track = el->track(); // keep note of element track el = el->parent(); // element with no parent (eg, a newly-added line) - no way to find context if (!el) return 0; switch (el->type()) { case ElementType::NOTE: // a note is a valid target trg = el; cr = toNote(el)->chord(); break; case ElementType::CHORD: // a chord or a rest are valid targets case ElementType::REST: trg = el; cr = toChordRest(trg); break; case ElementType::SEGMENT: { // from segment go to top chordrest in segment Segment* seg = toSegment(el); // if segment is not chord/rest or grace, move to next chord/rest or grace segment if (!seg->isChordRest()) { seg = seg->next1(SegmentType::ChordRest); if (seg == 0) // if none found, return failure return 0; } // segment for sure contains chords/rests, int size = int(seg->elist().size()); // if segment has a chord/rest in original element track, use it if (track > -1 && track < size && seg->element(track)) { trg = seg->element(track); cr = toChordRest(trg); break; } // if not, get topmost chord/rest for (int i = 0; i < size; i++) if (seg->element(i)) { trg = seg->element(i); cr = toChordRest(trg); break; } break; } default: // on anything else, return failure return 0; } // if something found and command is forward, the element found is the destination if (trg && cmd == "next-chord") { // if chord, go to topmost note if (trg->type() == ElementType::CHORD) trg = toChord(trg)->upNote(); setPlayNote(true); select(trg, SelectType::SINGLE, 0); return trg; } // if no chordrest found, do nothing if (cr == 0) return 0; // if some chordrest found, continue with default processing } Element* el = 0; Segment* ois = noteEntryMode() ? _is.segment() : nullptr; Measure* oim = ois ? ois->measure() : nullptr; if (cmd == "next-chord") { // note input cursor if (noteEntryMode()) _is.moveToNextInputPos(); // selection "cursor" // find next chordrest, which might be a grace note // this may override note input cursor el = nextChordRest(cr); while (el && el->isRest() && toRest(el)->isGap()) el = nextChordRest(toChordRest(el)); if (el && noteEntryMode()) { // do not use if not in original or new measure (don't skip measures) Measure* m = toChordRest(el)->measure(); Segment* nis = _is.segment(); Measure* nim = nis ? nis->measure() : nullptr; if (m != oim && m != nim) el = cr; // do not use if new input segment is current cr // this means input cursor just caught up to current selection else if (cr && nis == cr->segment()) el = cr; } else if (!el) el = cr; } else if (cmd == "prev-chord") { // note input cursor if (noteEntryMode() && _is.segment()) { Measure* m = _is.segment()->measure(); Segment* s = _is.segment()->prev1(SegmentType::ChordRest); int track = _is.track(); for (; s; s = s->prev1(SegmentType::ChordRest)) { if (s->element(track) || (s->measure() != m && s->rtick().isZero())) { if (s->element(track)) { if (s->element(track)->isRest() && toRest(s->element(track))->isGap()) continue; } break; } } _is.moveInputPos(s); } // selection "cursor" // find previous chordrest, which might be a grace note // this may override note input cursor el = prevChordRest(cr); while (el && el->isRest() && toRest(el)->isGap()) el = prevChordRest(toChordRest(el)); if (el && noteEntryMode()) { // do not use if not in original or new measure (don't skip measures) Measure* m = toChordRest(el)->measure(); Segment* nis = _is.segment(); Measure* nim = nis ? nis->measure() : nullptr; if (m != oim && m != nim) el = cr; // do not use if new input segment is current cr // this means input cursor just caught up to current selection else if (cr && nis == cr->segment()) el = cr; } else if (!el) el = cr; } else if (cmd == "next-measure") { el = nextMeasure(cr); if (noteEntryMode()) _is.moveInputPos(el); } else if (cmd == "prev-measure") { el = prevMeasure(cr); if (noteEntryMode()) _is.moveInputPos(el); } else if (cmd == "next-track") { el = nextTrack(cr); if (noteEntryMode()) _is.moveInputPos(el); } else if (cmd == "prev-track") { el = prevTrack(cr); if (noteEntryMode()) _is.moveInputPos(el); } if (el) { if (el->type() == ElementType::CHORD) el = toChord(el)->upNote(); // originally downNote setPlayNote(true); if (noteEntryMode()) { // if cursor moved into a gap, selection cannot follow // only select & play el if it was not already selected (does not normally happen) if (_is.cr() || !el->selected()) select(el, SelectType::SINGLE, 0); else setPlayNote(false); for (MuseScoreView* view : viewer) view->moveCursor(); } else { select(el, SelectType::SINGLE, 0); } } return el; } //--------------------------------------------------------- // selectMove //--------------------------------------------------------- Element* Score::selectMove(const QString& cmd) { ChordRest* cr; if (selection().activeCR()) cr = selection().activeCR(); else cr = selection().lastChordRest(); if (cr == 0 && noteEntryMode()) cr = inputState().cr(); if (cr == 0) return 0; ChordRest* el = 0; if (cmd == "select-next-chord") el = nextChordRest(cr, true); else if (cmd == "select-prev-chord") el = prevChordRest(cr, true); else if (cmd == "select-next-measure") el = nextMeasure(cr, true, true); else if (cmd == "select-prev-measure") el = prevMeasure(cr, true); else if (cmd == "select-begin-line") { Measure* measure = cr->segment()->measure()->system()->firstMeasure(); if (!measure) return 0; el = measure->first()->nextChordRest(cr->track()); } else if (cmd == "select-end-line") { Measure* measure = cr->segment()->measure()->system()->lastMeasure(); if (!measure) return 0; el = measure->last()->nextChordRest(cr->track(), true); } else if (cmd == "select-begin-score") { Measure* measure = firstMeasureMM(); if (!measure) return 0; el = measure->first()->nextChordRest(cr->track()); } else if (cmd == "select-end-score") { Measure* measure = lastMeasureMM(); if (!measure) return 0; el = measure->last()->nextChordRest(cr->track(), true); } else if (cmd == "select-staff-above") el = upStaff(cr); else if (cmd == "select-staff-below") el = downStaff(cr); if (el) select(el, SelectType::RANGE, el->staffIdx()); return el; } //--------------------------------------------------------- // cmdMirrorNoteHead //--------------------------------------------------------- void Score::cmdMirrorNoteHead() { const QList& el = selection().elements(); for (Element* e : el) { if (e->isNote()) { Note* note = toNote(e); if (note->staff() && note->staff()->isTabStaff(note->chord()->tick())) e->undoChangeProperty(Pid::GHOST, !note->ghost()); else { MScore::DirectionH d = note->userMirror(); if (d == MScore::DirectionH::AUTO) d = note->chord()->up() ? MScore::DirectionH::RIGHT : MScore::DirectionH::LEFT; else d = d == MScore::DirectionH::LEFT ? MScore::DirectionH::RIGHT : MScore::DirectionH::LEFT; undoChangeUserMirror(note, d); } } else if (e->isHairpinSegment()) { Hairpin* h = toHairpinSegment(e)->hairpin(); HairpinType st = h->hairpinType(); switch (st) { case HairpinType::CRESC_HAIRPIN: st = HairpinType::DECRESC_HAIRPIN; break; case HairpinType::DECRESC_HAIRPIN: st = HairpinType::CRESC_HAIRPIN; break; case HairpinType::CRESC_LINE: st = HairpinType::DECRESC_LINE; break; case HairpinType::DECRESC_LINE: st = HairpinType::CRESC_LINE; break; case HairpinType::INVALID: break; } h->undoChangeProperty(Pid::HAIRPIN_TYPE, int(st)); } } } //--------------------------------------------------------- // cmdIncDecDuration // When stepDotted is false and nSteps is 1 or -1, will halve or double the duration // When stepDotted is true, will step by nearest dotted or undotted note //--------------------------------------------------------- void Score::cmdIncDecDuration(int nSteps, bool stepDotted) { Element* el = selection().element(); if (el == 0) return; if (el->isNote()) el = el->parent(); if (!el->isChordRest()) return; ChordRest* cr = toChordRest(el); // if measure rest is selected as input, then the correct initialDuration will be the // duration of the measure's time signature, else is just the input state's duration TDuration initialDuration = (cr->durationType() == TDuration::DurationType::V_MEASURE) ? TDuration(cr->measure()->timesig()) : _is.duration(); TDuration d = initialDuration.shiftRetainDots(nSteps, stepDotted); if (!d.isValid()) return; if (cr->isChord() && (toChord(cr)->noteType() != NoteType::NORMAL)) { // // handle appoggiatura and acciaccatura // undoChangeChordRestLen(cr, d); } else changeCRlen(cr, d); _is.setDuration(d); nextInputPos(cr, false); } //--------------------------------------------------------- // cmdAddBracket //--------------------------------------------------------- void Score::cmdAddBracket() { for (Element* el : selection().elements()) { if (el->type() == ElementType::ACCIDENTAL) { Accidental* acc = toAccidental(el); acc->undoChangeProperty(Pid::ACCIDENTAL_BRACKET, int(AccidentalBracket::BRACKET)); } } } //--------------------------------------------------------- // cmdAddParentheses //--------------------------------------------------------- void Score::cmdAddParentheses() { for (Element* el : selection().elements()) { if (el->type() == ElementType::NOTE) { Note* n = toNote(el); n->addParentheses(); } else if (el->type() == ElementType::ACCIDENTAL) { Accidental* acc = toAccidental(el); acc->undoChangeProperty(Pid::ACCIDENTAL_BRACKET, int(AccidentalBracket::PARENTHESIS)); } else if (el->type() == ElementType::HARMONY) { Harmony* h = toHarmony(el); h->setLeftParen(true); h->setRightParen(true); h->render(); } else if (el->type() == ElementType::TIMESIG) { TimeSig* ts = toTimeSig(el); ts->setLargeParentheses(true); } } } //--------------------------------------------------------- // cmdMoveRest //--------------------------------------------------------- void Score::cmdMoveRest(Rest* rest, Direction dir) { QPointF pos(rest->offset()); if (dir == Direction::UP) pos.ry() -= spatium(); else if (dir == Direction::DOWN) pos.ry() += spatium(); rest->undoChangeProperty(Pid::OFFSET, pos); } //--------------------------------------------------------- // cmdMoveLyrics //--------------------------------------------------------- void Score::cmdMoveLyrics(Lyrics* lyrics, Direction dir) { int verse = lyrics->no() + (dir == Direction::UP ? -1 : 1); if (verse < 0) return; lyrics->undoChangeProperty(Pid::VERSE, verse); } //--------------------------------------------------------- // cmdInsertClef //--------------------------------------------------------- void Score::cmdInsertClef(ClefType type) { if (!noteEntryMode()) return; undoChangeClef(staff(inputTrack()/VOICES), inputState().cr(), type); } //--------------------------------------------------------- // cmdInsertClef // insert clef before cr //--------------------------------------------------------- void Score::cmdInsertClef(Clef* clef, ChordRest* cr) { undoChangeClef(cr->staff(), cr, clef->clefType()); delete clef; } //--------------------------------------------------------- // cmdAddGrace /// adds grace note of specified type to selected notes //--------------------------------------------------------- void Score::cmdAddGrace (NoteType graceType, int duration) { const QList copyOfElements = selection().elements(); for (Element* e : copyOfElements) { if (e->type() == ElementType::NOTE) { Note* n = toNote(e); setGraceNote(n->chord(), n->pitch(), graceType, duration); } } } //--------------------------------------------------------- // cmdExplode /// explodes contents of top selected staff into subsequent staves //--------------------------------------------------------- void Score::cmdExplode() { if (!selection().isRange()) return; int srcStaff = selection().staffStart(); int lastStaff = selection().staffEnd(); int srcTrack = srcStaff * VOICES; // reset selection to top staff only // force complete measures Segment* startSegment = selection().startSegment(); Segment* endSegment = selection().endSegment(); Measure* startMeasure = startSegment->measure(); Measure* endMeasure = endSegment ? endSegment->measure() : lastMeasure(); Fraction lTick = endMeasure->endTick(); bool voice = false; for (Measure* m = startMeasure; m && m->tick() != lTick; m = m->nextMeasure()) { if (m->hasVoices(srcStaff)) { voice = true; break; } } if (! voice) { // force complete measures deselectAll(); select(startMeasure, SelectType::RANGE, srcStaff); select(endMeasure, SelectType::RANGE, srcStaff); startSegment = selection().startSegment(); endSegment = selection().endSegment(); if (srcStaff == lastStaff - 1) { // only one staff was selected up front - determine number of staves // loop through all chords looking for maximum number of notes int n = 0; for (Segment* s = startSegment; s && s != endSegment; s = s->next1()) { Element* e = s->element(srcTrack); if (e && e->type() == ElementType::CHORD) { Chord* c = toChord(e); n = qMax(n, int(c->notes().size())); } } lastStaff = qMin(nstaves(), srcStaff + n); } const QByteArray mimeData(selection().mimeData()); // copy to all destination staves Segment* firstCRSegment = startMeasure->tick2segment(startMeasure->tick()); for (int i = 1; srcStaff + i < lastStaff; ++i) { int track = (srcStaff + i) * VOICES; ChordRest* cr = toChordRest(firstCRSegment->element(track)); if (cr) { XmlReader e(mimeData); e.setPasteMode(true); pasteStaff(e, cr->segment(), cr->staffIdx()); } } // loop through each staff removing all but one note from each chord for (int i = 0; srcStaff + i < lastStaff; ++i) { int track = (srcStaff + i) * VOICES; for (Segment* s = startSegment; s && s != endSegment; s = s->next1()) { Element* e = s->element(track); if (e && e->type() == ElementType::CHORD) { Chord* c = toChord(e); std::vector notes = c->notes(); int nnotes = int(notes.size()); // keep note "i" from top, which is backwards from nnotes - 1 // reuse notes if there are more instruments than notes int stavesPerNote = qMax((lastStaff - srcStaff) / nnotes, 1); int keepIndex = qMax(nnotes - 1 - (i / stavesPerNote), 0); Note* keepNote = c->notes()[keepIndex]; foreach (Note* n, notes) { if (n != keepNote) undoRemoveElement(n); } } } } } else { int sTracks[VOICES]; int dTracks[VOICES]; if (srcStaff == lastStaff - 1) lastStaff = qMin(nstaves(), srcStaff + VOICES); for (int i = 0; i < VOICES; i++) { sTracks[i] = -1; dTracks[i] = -1; } int full = 0; for (Segment* seg = startSegment; seg && seg->tick() < lTick; seg = seg->next1()) { for (int i = srcTrack; i < srcTrack + VOICES && full != VOICES; i++) { bool t = true; for (int j = 0; j < VOICES; j++) { if (i == sTracks[j]) { t = false; break; } } if (!seg->measure()->hasVoice(i) || seg->measure()->isOnlyRests(i) || !t) continue; sTracks[full] = i; for(int j = srcTrack + full * VOICES; j < lastStaff * VOICES; j++) { if (i == j) { dTracks[full] = j; break; } for(Measure* m = seg->measure(); m && m->tick() < lTick; m = m->nextMeasure()) { if (!m->hasVoice(j) || (m->hasVoice(j) && m->isOnlyRests(j))) dTracks[full] = j; else { dTracks[full] = -1; break; } } if (dTracks[full] != -1) break; } full++; } } for (int i = srcTrack, j = 0; i < lastStaff * VOICES && j < VOICES ; i += VOICES, j++) { int strack = sTracks[j % VOICES]; int dtrack = dTracks[j % VOICES]; if (strack != -1 && strack != dtrack && dtrack != -1) undo(new CloneVoice(startSegment, lTick, startSegment, strack, dtrack, -1, false)); } } // select exploded region deselectAll(); select(startMeasure, SelectType::RANGE, srcStaff); select(endMeasure, SelectType::RANGE, lastStaff - 1); } //--------------------------------------------------------- // cmdImplode /// implodes contents of selected staves into top staff /// for single staff, merge voices //--------------------------------------------------------- void Score::cmdImplode() { if (!selection().isRange()) return; int dstStaff = selection().staffStart(); int endStaff = selection().staffEnd(); int dstTrack = dstStaff * VOICES; int startTrack = dstStaff * VOICES; int endTrack = endStaff * VOICES; Segment* startSegment = selection().startSegment(); Segment* endSegment = selection().endSegment(); Measure* startMeasure = startSegment->measure(); Measure* endMeasure = endSegment ? endSegment->measure() : lastMeasure(); Fraction startTick = startSegment->tick(); Fraction endTick = endSegment ? endSegment->tick() : lastMeasure()->endTick(); Q_ASSERT(startMeasure && endMeasure); // if single staff selected, combine voices // otherwise combine staves if (dstStaff == endStaff - 1) { // loop through segments adding notes to chord on top staff for (Segment* s = startSegment; s && s != endSegment; s = s->next1()) { if (!s->isChordRestType()) continue; Element* dst = s->element(dstTrack); if (dst && dst->isChord()) { Chord* dstChord = toChord(dst); // see if we are tying in to this chord Chord* tied = 0; for (Note* n : dstChord->notes()) { if (n->tieBack()) { tied = n->tieBack()->startNote()->chord(); break; } } // loop through each subsequent staff (or track within staff) // looking for notes to add for (int srcTrack = startTrack + 1; srcTrack < endTrack; srcTrack++) { Element* src = s->element(srcTrack); if (src && src->isChord()) { Chord* srcChord = toChord(src); // when combining voices, skip if not same duration if (srcChord->ticks() != dstChord->ticks()) continue; // add notes for (Note* n : srcChord->notes()) { NoteVal nv(n->pitch()); nv.tpc1 = n->tpc1(); // skip duplicates if (dstChord->findNote(nv.pitch)) continue; Note* nn = addNote(dstChord, nv); // add tie to this note if original chord was tied if (tied) { // find note to tie to for (Note *tn : tied->notes()) { if (nn->pitch() == tn->pitch() && nn->tpc() == tn->tpc() && !tn->tieFor()) { // found note to tie Tie* tie = new Tie(this); tie->setStartNote(tn); tie->setEndNote(nn); tie->setTrack(tn->track()); undoAddElement(tie); } } } } } // delete chordrest from source track if possible if (src && src->voice()) undoRemoveElement(src); } } // TODO - use first voice that actually has a note and implode remaining voices on it? // see https://musescore.org/en/node/174111 else if (dst) { // destination track has something, but it isn't a chord // remove rests from other voices if in "voice mode" for (int i = 1; i < VOICES; ++i) { Element* e = s->element(dstTrack + i); if (e && e->isRest()) undoRemoveElement(e); } } } // delete orphaned spanners (TODO: figure out solution to reconnect orphaned spanners to their cloned notes) checkSpanner(startTick, endTick); } else { int tracks[VOICES]; for (int i = 0; i < VOICES; i++) tracks[i] = -1; int full = 0; // identify tracks to combine, storing the source track numbers in tracks[] // first four non-empty tracks to win for (int track = startTrack; track < endTrack && full < VOICES; ++track) { Measure* m = startMeasure; do { if (m->hasVoice(track) && !m->isOnlyRests(track)) { tracks[full++] = track; break; } } while ((m != endMeasure) && (m = m->nextMeasure())); } // clone source tracks into destination for (int i = dstTrack; i < dstTrack + VOICES; i++) { int strack = tracks[i % VOICES]; if (strack != -1 && strack != i) { undo( new CloneVoice(startSegment, endTick, startSegment, strack, i, i, false)); } } } // select destination staff only deselectAll(); select(startMeasure, SelectType::RANGE, dstStaff); select(endMeasure, SelectType::RANGE, dstStaff); } //--------------------------------------------------------- // cmdSlashFill /// fills selected region with slashes //--------------------------------------------------------- void Score::cmdSlashFill() { int startStaff = selection().staffStart(); int endStaff = selection().staffEnd(); Segment* startSegment = selection().startSegment(); if (!startSegment) // empty score? return; Segment* endSegment = selection().endSegment(); // operate on measures underlying mmrests if (startSegment && startSegment->measure() && startSegment->measure()->isMMRest()) startSegment = startSegment->measure()->mmRestFirst()->first(); if (endSegment && endSegment->measure() && endSegment->measure()->isMMRest()) endSegment = endSegment->measure()->mmRestLast()->last(); Fraction endTick = endSegment ? endSegment->tick() : lastSegment()->tick() + Fraction::fromTicks(1); Chord* firstSlash = 0; Chord* lastSlash = 0; // loop through staves in selection for (int staffIdx = startStaff; staffIdx < endStaff; ++staffIdx) { int track = staffIdx * VOICES; int voice = -1; // loop through segments adding slashes on each beat for (Segment* s = startSegment; s && s->tick() < endTick; s = s->next1()) { if (s->segmentType() != SegmentType::ChordRest) continue; // determine beat type based on time signature int d = s->measure()->timesig().denominator(); int n = (d > 4 && s->measure()->timesig().numerator() % 3 == 0) ? 3 : 1; Fraction f(n, d); // skip over any leading segments before next (first) beat if (s->rtick().ticks() % f.ticks()) continue; // determine voice to use - first available voice for this measure / staff if (voice == -1 || s->rtick().isZero()) { bool needGap[VOICES]; for (voice = 0; voice < VOICES; ++voice) { needGap[voice] = false; ChordRest* cr = toChordRest(s->element(track + voice)); // no chordrest == treat as ordinary rest for purpose of determining availbility of voice // but also, we will need to make a gap for this voice if we do end up choosing it if (!cr) needGap[voice] = true; // chord == keep looking for an available voice else if (cr->type() == ElementType::CHORD) continue; // full measure rest == OK to use voice else if (cr->durationType() == TDuration::DurationType::V_MEASURE) break; // no chordrest or ordinary rest == OK to use voice // if there are nothing but rests for duration of measure / selection bool ok = true; for (Segment* ns = s->next(SegmentType::ChordRest); ns && ns != endSegment; ns = ns->next(SegmentType::ChordRest)) { ChordRest* ncr = toChordRest(ns->element(track + voice)); if (ncr && ncr->type() == ElementType::CHORD) { ok = false; break; } } if (ok) break; } // no available voices, just use voice 0 if (voice == VOICES) voice = 0; // no cr was found in segment for this voice, so make gap if (needGap[voice]) makeGapVoice(s, track + voice, f, s->tick()); } // construct note int line = 0; bool error = false; NoteVal nv; if (staff(staffIdx)->staffType(s->tick())->group() == StaffGroup::TAB) line = staff(staffIdx)->lines(s->tick()) / 2; else line = staff(staffIdx)->middleLine(s->tick()); // staff(staffIdx)->lines() - 1; if (staff(staffIdx)->staffType(s->tick())->group() == StaffGroup::PERCUSSION) { nv.pitch = 0; nv.headGroup = NoteHead::Group::HEAD_SLASH; } else { Position p; p.segment = s; p.staffIdx = staffIdx; p.line = line; p.fret = FRET_NONE; _is.setRest(false); // needed for tab nv = noteValForPosition(p, AccidentalType::NONE, error); } if (error) continue; // insert & turn into slash s = setNoteRest(s, track + voice, nv, f); Chord* c = toChord(s->element(track + voice)); if (c->links()) { for (ScoreElement* e : *c->links()) { Chord* lc = toChord(e); lc->setSlash(true, true); } } else c->setSlash(true, true); lastSlash = c; if (!firstSlash) firstSlash = c; } } // re-select the slashes deselectAll(); if (firstSlash && lastSlash) { select(firstSlash, SelectType::RANGE); select(lastSlash, SelectType::RANGE); } } //--------------------------------------------------------- // cmdSlashRhythm /// converts rhythms in selected region to slashes //--------------------------------------------------------- void Score::cmdSlashRhythm() { QList chords; // loop through all notes in selection foreach (Element* e, selection().elements()) { if (e->voice() >= 2 && e->isRest()) { Rest* r = toRest(e); if (r->links()) { for (ScoreElement* se : *r->links()) { Rest* lr = toRest(se); lr->setAccent(!lr->accent()); } } else r->setAccent(!r->accent()); continue; } else if (e->isNote()) { Note* n = toNote(e); if (n->noteType() != NoteType::NORMAL) continue; Chord* c = n->chord(); // check for duplicates (chords with multiple notes) if (chords.contains(c)) continue; chords.append(c); // toggle slash setting if (c->links()) { for (ScoreElement* se : *c->links()) { Chord* lc = toChord(se); lc->setSlash(!lc->slash(), false); } } else c->setSlash(!c->slash(), false); } } } //--------------------------------------------------------- // cmdResequenceRehearsalMarks /// resequences rehearsal marks within a range selection /// or, if nothing is selected, the entire score //--------------------------------------------------------- void Score::cmdResequenceRehearsalMarks() { bool noSelection = !selection().isRange(); if (noSelection) cmdSelectAll(); else if (!selection().isRange()) return; RehearsalMark* last = 0; for (Segment* s = selection().startSegment(); s && s != selection().endSegment(); s = s->next1()) { for (Element* e : s->annotations()) { if (e->type() == ElementType::REHEARSAL_MARK) { RehearsalMark* rm = toRehearsalMark(e); if (last) { QString rmText = nextRehearsalMarkText(last, rm); for (ScoreElement* le : rm->linkList()) le->undoChangeProperty(Pid::TEXT, rmText); } last = rm; } } } if (noSelection) deselectAll(); } //--------------------------------------------------------- // addRemoveBreaks // interval lock // 0 false remove all linebreaks // > 0 false add linebreak every interval measure // d.c. true add linebreak at every system end //--------------------------------------------------------- void Score::addRemoveBreaks(int interval, bool lock) { Segment* startSegment = selection().startSegment(); if (!startSegment) // empty score? return; Segment* endSegment = selection().endSegment(); Measure* startMeasure = startSegment->measure(); Measure* endMeasure = endSegment ? endSegment->measure() : lastMeasureMM(); Measure* lastMeasure = lastMeasureMM(); // loop through measures in selection // count mmrests as a single measure int count = 0; for (Measure* mm = startMeasure; mm; mm = mm->nextMeasureMM()) { // even though we are counting mmrests as a single measure, // we need to find last real measure within mmrest for the actual break Measure* m = mm->isMMRest() ? mm->mmRestLast() : mm; if (lock) { // skip last measure of score if (mm == lastMeasure) break; // skip if it already has a break if (m->lineBreak() || m->pageBreak()) continue; // add break if last measure of system if (mm->system() && mm->system()->lastMeasure() == mm) m->undoSetLineBreak(true); } else { if (interval == 0) { // remove line break if present if (m->lineBreak()) m->undoSetLineBreak(false); } else { if (++count == interval) { // skip last measure of score if (mm == lastMeasure) break; // found place for break; add if not already one present if (!(m->lineBreak() || m->pageBreak())) m->undoSetLineBreak(true); // reset count count = 0; } else if (m->lineBreak()) { // remove line break if present in wrong place m->undoSetLineBreak(false); } } } if (mm == endMeasure) break; } } //--------------------------------------------------------- // cmdRemoveEmptyTrailingMeasures //--------------------------------------------------------- void Score::cmdRemoveEmptyTrailingMeasures() { MasterScore* score = masterScore(); Measure* firstMeasure; Measure* lastMeasure = score->lastMeasure(); if (!lastMeasure || !lastMeasure->isFullMeasureRest()) return; firstMeasure = lastMeasure; for (firstMeasure = lastMeasure;;) { Measure* m = firstMeasure->prevMeasure(); if (!m || !m->isFullMeasureRest()) break; firstMeasure = m; } deleteMeasures(firstMeasure, lastMeasure); } //--------------------------------------------------------- // cmdPitchUp //--------------------------------------------------------- void Score::cmdPitchUp() { Element* el = selection().element(); if (el && el->isLyrics()) cmdMoveLyrics(toLyrics(el), Direction::UP); else if (el && (el->isArticulation() || el->isTextBase())) el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF(0.0, -MScore::nudgeStep * el->spatium()), PropertyFlags::UNSTYLED); else if (el && el->isRest()) cmdMoveRest(toRest(el), Direction::UP); else upDown(true, UpDownMode::CHROMATIC); } //--------------------------------------------------------- // cmdPitchDown //--------------------------------------------------------- void Score::cmdPitchDown() { Element* el = selection().element(); if (el && el->isLyrics()) cmdMoveLyrics(toLyrics(el), Direction::DOWN); else if (el && (el->isArticulation() || el->isTextBase())) el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF(0.0, MScore::nudgeStep * el->spatium()), PropertyFlags::UNSTYLED); else if (el && el->isRest()) cmdMoveRest(toRest(el), Direction::DOWN); else upDown(false, UpDownMode::CHROMATIC); } //--------------------------------------------------------- // cmdTimeDelete //--------------------------------------------------------- void Score::cmdTimeDelete() { Element* e = selection().element(); if (e && e->isBarLine() && toBarLine(e)->segment()->isEndBarLineType()) { Measure* m = toBarLine(e)->segment()->measure(); cmdJoinMeasure(m, m->nextMeasure()); } else { if (_is.insertMode()) globalTimeDelete(); else localTimeDelete(); } } //--------------------------------------------------------- // cmdPitchUpOctave //--------------------------------------------------------- void Score::cmdPitchUpOctave() { Element* el = selection().element(); if (el && (el->isArticulation() || el->isTextBase())) el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF(0.0, -MScore::nudgeStep10 * el->spatium()), PropertyFlags::UNSTYLED); else upDown(true, UpDownMode::OCTAVE); } //--------------------------------------------------------- // cmdPitchDownOctave //--------------------------------------------------------- void Score::cmdPitchDownOctave() { Element* el = selection().element(); if (el && (el->isArticulation() || el->isTextBase())) el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF(0.0, MScore::nudgeStep10 * el->spatium()), PropertyFlags::UNSTYLED); else upDown(false, UpDownMode::OCTAVE); } //--------------------------------------------------------- // cmdPadNoteInclreaseTAB //--------------------------------------------------------- void Score::cmdPadNoteIncreaseTAB(const EditData& ed) { switch (_is.duration().type() ) { // cycle back from longest to shortest? // case TDuration::V_LONG: // padToggle(Pad::NOTE128, ed); // break; case TDuration::DurationType::V_BREVE: padToggle(Pad::NOTE00, ed); break; case TDuration::DurationType::V_WHOLE: padToggle(Pad::NOTE0, ed); break; case TDuration::DurationType::V_HALF: padToggle(Pad::NOTE1, ed); break; case TDuration::DurationType::V_QUARTER: padToggle(Pad::NOTE2, ed); break; case TDuration::DurationType::V_EIGHTH: padToggle(Pad::NOTE4, ed); break; case TDuration::DurationType::V_16TH: padToggle(Pad::NOTE8, ed); break; case TDuration::DurationType::V_32ND: padToggle(Pad::NOTE16, ed); break; case TDuration::DurationType::V_64TH: padToggle(Pad::NOTE32, ed); break; case TDuration::DurationType::V_128TH: padToggle(Pad::NOTE64, ed); break; default: break; } } //--------------------------------------------------------- // cmdPadNoteDecreaseTAB //--------------------------------------------------------- void Score::cmdPadNoteDecreaseTAB(const EditData& ed) { switch (_is.duration().type() ) { case TDuration::DurationType::V_LONG: padToggle(Pad::NOTE0, ed); break; case TDuration::DurationType::V_BREVE: padToggle(Pad::NOTE1, ed); break; case TDuration::DurationType::V_WHOLE: padToggle(Pad::NOTE2, ed); break; case TDuration::DurationType::V_HALF: padToggle(Pad::NOTE4, ed); break; case TDuration::DurationType::V_QUARTER: padToggle(Pad::NOTE8, ed); break; case TDuration::DurationType::V_EIGHTH: padToggle(Pad::NOTE16, ed); break; case TDuration::DurationType::V_16TH: padToggle(Pad::NOTE32, ed); break; case TDuration::DurationType::V_32ND: padToggle(Pad::NOTE64, ed); break; case TDuration::DurationType::V_64TH: padToggle(Pad::NOTE128, ed); break; // cycle back from shortest to longest? // case TDuration::DurationType::V_128TH: // padToggle(Pad::NOTE00, ed); // break; default: break; } } //--------------------------------------------------------- // cmdToggleLayoutBreak //--------------------------------------------------------- void Score::cmdToggleLayoutBreak(LayoutBreak::Type type) { // find measure(s) QList mbl; if (selection().isRange()) { Measure* startMeasure = nullptr; Measure* endMeasure = nullptr; if (!selection().measureRange(&startMeasure, &endMeasure)) return; if (!startMeasure || !endMeasure) return; #if 1 // toggle break on the last measure of the range mbl.append(endMeasure); // if more than one measure selected, // also toggle break *before* the range (to try to fit selection on a single line) if (startMeasure != endMeasure && startMeasure->prev()) mbl.append(startMeasure->prev()); #else // toggle breaks throughout the selection for (Measure* m = startMeasure; m; m = m->nextMeasure()) { mbl.append(m); if (m == endMeasure) break; } #endif } else { MeasureBase* mb = nullptr; for (Element* el : selection().elements()) { switch (el->type()) { case ElementType::HBOX: case ElementType::VBOX: case ElementType::TBOX: mb = toMeasureBase(el); break; default: { // find measure Measure* measure = toMeasure(el->findMeasure()); // for start repeat, attach break to previous measure if (measure && el->isBarLine()) { BarLine* bl = toBarLine(el); if (bl->barLineType() == BarLineType::START_REPEAT) measure = measure->prevMeasure(); } // if measure is mmrest, then propagate to last original measure if (measure) mb = measure->isMMRest() ? measure->mmRestLast() : measure; } } } if (mb) mbl.append(mb); } // toggle the breaks for (MeasureBase* mb: mbl) { if (mb) { bool val = false; switch (type) { case LayoutBreak::Type::LINE: val = !mb->lineBreak(); mb->undoSetBreak(val, type); // remove page break if appropriate if (val && mb->pageBreak()) mb->undoSetBreak(false, LayoutBreak::Type::PAGE); break; case LayoutBreak::Type::PAGE: val = !mb->pageBreak(); mb->undoSetBreak(val, type); // remove line break if appropriate if (val && mb->lineBreak()) mb->undoSetBreak(false, LayoutBreak::Type::LINE); break; case LayoutBreak::Type::SECTION: val = !mb->sectionBreak(); mb->undoSetBreak(val, type); break; default: break; } } } } //--------------------------------------------------------- // cmdToggleMmrest //--------------------------------------------------------- void Score::cmdToggleMmrest() { bool val = !styleB(Sid::createMultiMeasureRests); deselectAll(); undo(new ChangeStyleVal(this, Sid::createMultiMeasureRests, val)); } //--------------------------------------------------------- // cmdToggleHideEmpty //--------------------------------------------------------- void Score::cmdToggleHideEmpty() { bool val = !styleB(Sid::hideEmptyStaves); deselectAll(); undo(new ChangeStyleVal(this, Sid::hideEmptyStaves, val)); } //--------------------------------------------------------- // cmdSetVisible //--------------------------------------------------------- void Score::cmdSetVisible() { for (Element* e : selection().elements()) undo(new ChangeProperty(e, Pid::VISIBLE, true)); } //--------------------------------------------------------- // cmdUnsetVisible //--------------------------------------------------------- void Score::cmdUnsetVisible() { for (Element* e : selection().elements()) undo(new ChangeProperty(e, Pid::VISIBLE, false)); } //--------------------------------------------------------- // cmdAddPitch /// insert note or add note to chord // c d e f g a b entered: //--------------------------------------------------------- void Score::cmdAddPitch(const EditData& ed, int note, bool addFlag, bool insert) { InputState& is = inputState(); if (is.track() == -1) // invalid state return; if (is.segment() == 0) { qDebug("cannot enter notes here (no chord rest at current position)"); return; } is.setRest(false); const Drumset* ds = is.drumset(); int octave = 4; if (ds) { char note1 = "CDEFGAB"[note]; int pitch = -1; int voice = 0; for (int i = 0; i < 127; ++i) { if (!ds->isValid(i)) continue; if (ds->shortcut(i) && (ds->shortcut(i) == note1)) { pitch = i; voice = ds->voice(i); break; } } if (pitch == -1) { qDebug(" shortcut %c not defined in drumset", note1); return; } is.setDrumNote(pitch); is.setTrack((is.track() / VOICES) * VOICES + voice); octave = pitch / 12; if (is.segment()) { Segment* seg = is.segment(); while (seg) { if (seg->element(is.track())) break; seg = seg->prev(SegmentType::ChordRest); } if (seg) is.setSegment(seg); else is.setSegment(is.segment()->measure()->first(SegmentType::ChordRest)); } } else { static const int tab[] = { 0, 2, 4, 5, 7, 9, 11 }; // if adding notes, add above the upNote of the current chord Element* el = selection().element(); if (addFlag && el && el->isNote()) { Chord* chord = toNote(el)->chord(); Note* n = chord->upNote(); octave = n->epitch() / 12; int tpc = n->tpc(); if (tpc == Tpc::TPC_C_BB || tpc == Tpc::TPC_C_B) ++octave; else if (tpc == Tpc::TPC_B_S || tpc == Tpc::TPC_B_SS) --octave; if (note <= tpc2step(tpc)) octave++; } else { int curPitch = 60; if (is.segment()) { Staff* staff = Score::staff(is.track() / VOICES); Segment* seg = is.segment()->prev1(SegmentType::ChordRest | SegmentType::Clef | SegmentType::HeaderClef); while (seg) { if (seg->isChordRestType()) { Element* p = seg->element(is.track()); if (p && p->isChord()) { curPitch = toChord(p)->downNote()->epitch(); break; } } else if (seg->isClefType() || seg->isHeaderClefType()) { Element* p = seg->element( (is.track() / VOICES) * VOICES); // clef on voice 1 if (p && p->isClef()) { Clef* clef = toClef(p); // check if it's an actual change or just a courtesy ClefType ctb = staff->clef(clef->tick() - Fraction::fromTicks(1)); if (ctb != clef->clefType() || clef->tick().isZero()) { curPitch = line2pitch(4, clef->clefType(), Key::C); // C 72 for treble clef break; } } } seg = seg->prev1MM(SegmentType::ChordRest | SegmentType::Clef | SegmentType::HeaderClef); } octave = curPitch / 12; } int delta = octave * 12 + tab[note] - curPitch; if (delta > 6) --octave; else if (delta < -6) ++octave; } } ed.view->startNoteEntryMode(); int step = octave * 7 + note; cmdAddPitch(step, addFlag, insert); ed.view->adjustCanvasPosition(is.cr(), false); } void Score::cmdAddPitch(int step, bool addFlag, bool insert) { insert = insert || inputState().usingNoteEntryMethod(NoteEntryMethod::TIMEWISE); Position pos; if (addFlag) { Element* el = selection().element(); if (el && el->isNote()) { Note* selectedNote = toNote(el); Chord* chord = selectedNote->chord(); Segment* seg = chord->segment(); pos.segment = seg; pos.staffIdx = selectedNote->track() / VOICES; ClefType clef = staff(pos.staffIdx)->clef(seg->tick()); pos.line = relStep(step, clef); bool error; NoteVal nval = noteValForPosition(pos, _is.accidentalType(), error); if (error) return; bool forceAccidental = false; if (_is.accidentalType() != AccidentalType::NONE) { NoteVal nval2 = noteValForPosition(pos, AccidentalType::NONE, error); forceAccidental = (nval.pitch == nval2.pitch); } addNote(chord, nval, forceAccidental); _is.setAccidentalType(AccidentalType::NONE); return; } } pos.segment = inputState().segment(); pos.staffIdx = inputState().track() / VOICES; ClefType clef = staff(pos.staffIdx)->clef(pos.segment->tick()); pos.line = relStep(step, clef); if (inputState().usingNoteEntryMethod(NoteEntryMethod::REPITCH)) repitchNote(pos, !addFlag); else { if (insert) insertChord(pos); else putNote(pos, !addFlag); } _is.setAccidentalType(AccidentalType::NONE); } //--------------------------------------------------------- // cmdToggleVisible //--------------------------------------------------------- void Score::cmdToggleVisible() { QSet spanners; for (Element* e : selection().elements()) { if (e->isBracket()) // ignore continue; if (e->isNoteDot() && selection().elements().contains(e->parent())) // already handled in ScoreElement::undoChangeProperty(); don't toggle twice continue; bool spannerSegment = e->isSpannerSegment(); if (!spannerSegment || !spanners.contains(toSpannerSegment(e)->spanner())) e->undoChangeProperty(Pid::VISIBLE, !e->getProperty(Pid::VISIBLE).toBool()); if (spannerSegment) spanners.insert(toSpannerSegment(e)->spanner()); } } //--------------------------------------------------------- // cmdAddFret /// insert note with given fret on current string //--------------------------------------------------------- void Score::cmdAddFret(int fret) { InputState& is = inputState(); if (is.track() == -1) // invalid state return; if (!is.segment()) { qDebug("cannot enter notes here (no chord rest at current position)"); return; } Position pos; pos.segment = is.segment(); pos.staffIdx = is.track() / VOICES; pos.line = is.string(); pos.fret = fret; putNote(pos, false); } //--------------------------------------------------------- // cmdRelayout //--------------------------------------------------------- void Score::cmdRelayout() { setLayoutAll(); } //--------------------------------------------------------- // cmdToggleAutoplace //--------------------------------------------------------- void Score::cmdToggleAutoplace(bool all) { if (all) { bool val = !styleB(Sid::autoplaceEnabled); undoChangeStyleVal(Sid::autoplaceEnabled, val); setLayoutAll(); } else { QSet spanners; for (Element* e : selection().elements()) { if (e->isSpannerSegment()) { if (Element* ee = e->propertyDelegate(Pid::AUTOPLACE)) e = ee; // spanner segments may each have their own autoplace setting // but if they delegate to spanner, only toggle once if (e->isSpanner()) { if (spanners.contains(e)) continue; spanners.insert(e); } } PropertyFlags pf = e->propertyFlags(Pid::AUTOPLACE); if (pf == PropertyFlags::STYLED) pf = PropertyFlags::UNSTYLED; e->undoChangeProperty(Pid::AUTOPLACE, !e->getProperty(Pid::AUTOPLACE).toBool(), pf); } } } //--------------------------------------------------------- // cmd //--------------------------------------------------------- void Score::cmd(const QAction* a, EditData& ed) { QString cmd(a ? a->data().toString() : ""); if (MScore::debugMode) qDebug("<%s>", qPrintable(cmd)); struct ScoreCmd { const char* name; std::function cmd; }; const std::vector cmdList { { "note-c", [this,ed]{ cmdAddPitch(ed, 0, false, false); }}, { "note-d", [this,ed]{ cmdAddPitch(ed, 1, false, false); }}, { "note-e", [this,ed]{ cmdAddPitch(ed, 2, false, false); }}, { "note-f", [this,ed]{ cmdAddPitch(ed, 3, false, false); }}, { "note-g", [this,ed]{ cmdAddPitch(ed, 4, false, false); }}, { "note-a", [this,ed]{ cmdAddPitch(ed, 5, false, false); }}, { "note-b", [this,ed]{ cmdAddPitch(ed, 6, false, false); }}, { "chord-c", [this,ed]{ cmdAddPitch(ed, 0, true, false); }}, { "chord-d", [this,ed]{ cmdAddPitch(ed, 1, true, false); }}, { "chord-e", [this,ed]{ cmdAddPitch(ed, 2, true, false); }}, { "chord-f", [this,ed]{ cmdAddPitch(ed, 3, true, false); }}, { "chord-g", [this,ed]{ cmdAddPitch(ed, 4, true, false); }}, { "chord-a", [this,ed]{ cmdAddPitch(ed, 5, true, false); }}, { "chord-b", [this,ed]{ cmdAddPitch(ed, 6, true, false); }}, { "insert-c", [this,ed]{ cmdAddPitch(ed, 0, false, true); }}, { "insert-d", [this,ed]{ cmdAddPitch(ed, 1, false, true); }}, { "insert-e", [this,ed]{ cmdAddPitch(ed, 2, false, true); }}, { "insert-f", [this,ed]{ cmdAddPitch(ed, 3, false, true); }}, { "insert-g", [this,ed]{ cmdAddPitch(ed, 4, false, true); }}, { "insert-a", [this,ed]{ cmdAddPitch(ed, 5, false, true); }}, { "insert-b", [this,ed]{ cmdAddPitch(ed, 6, false, true); }}, { "fret-0", [this]{ cmdAddFret(0); }}, { "fret-1", [this]{ cmdAddFret(1); }}, { "fret-2", [this]{ cmdAddFret(2); }}, { "fret-3", [this]{ cmdAddFret(3); }}, { "fret-4", [this]{ cmdAddFret(4); }}, { "fret-5", [this]{ cmdAddFret(5); }}, { "fret-6", [this]{ cmdAddFret(6); }}, { "fret-7", [this]{ cmdAddFret(7); }}, { "fret-8", [this]{ cmdAddFret(8); }}, { "fret-9", [this]{ cmdAddFret(9); }}, { "fret-10", [this]{ cmdAddFret(10); }}, { "fret-11", [this]{ cmdAddFret(11); }}, { "fret-12", [this]{ cmdAddFret(12); }}, { "fret-13", [this]{ cmdAddFret(13); }}, { "fret-14", [this]{ cmdAddFret(14); }}, { "toggle-visible", [this]{ cmdToggleVisible(); }}, { "reset-stretch", [this]{ resetUserStretch(); }}, { "mirror-note", [this]{ cmdMirrorNoteHead(); }}, { "double-duration", [this]{ cmdDoubleDuration(); }}, { "half-duration", [this]{ cmdHalfDuration(); }}, { "inc-duration-dotted", [this]{ cmdIncDurationDotted(); }}, { "dec-duration-dotted", [this]{ cmdDecDurationDotted(); }}, { "add-staccato", [this]{ addArticulation(SymId::articStaccatoAbove); }}, { "add-tenuto", [this]{ addArticulation(SymId::articTenutoAbove); }}, { "add-marcato", [this]{ addArticulation(SymId::articMarcatoAbove); }}, { "add-sforzato", [this]{ addArticulation(SymId::articAccentAbove); }}, { "add-trill", [this]{ addArticulation(SymId::ornamentTrill); }}, { "add-up-bow", [this]{ addArticulation(SymId::stringsUpBow); }}, { "add-down-bow", [this]{ addArticulation(SymId::stringsDownBow); }}, { "add-8va", [this]{ cmdAddOttava(OttavaType::OTTAVA_8VA); }}, { "add-8vb", [this]{ cmdAddOttava(OttavaType::OTTAVA_8VB); }}, { "note-longa", [this,ed]{ padToggle(Pad::NOTE00, ed); }}, { "note-longa-TAB", [this,ed]{ padToggle(Pad::NOTE00, ed); }}, { "note-breve", [this,ed]{ padToggle(Pad::NOTE0, ed); }}, { "note-breve-TAB", [this,ed]{ padToggle(Pad::NOTE0, ed); }}, { "pad-note-1", [this,ed]{ padToggle(Pad::NOTE1, ed); }}, { "pad-note-1-TAB", [this,ed]{ padToggle(Pad::NOTE1, ed); }}, { "pad-note-2", [this,ed]{ padToggle(Pad::NOTE2, ed); }}, { "pad-note-2-TAB", [this,ed]{ padToggle(Pad::NOTE2, ed); }}, { "pad-note-4", [this,ed]{ padToggle(Pad::NOTE4, ed); }}, { "pad-note-4-TAB", [this,ed]{ padToggle(Pad::NOTE4, ed); }}, { "pad-note-8", [this,ed]{ padToggle(Pad::NOTE8, ed); }}, { "pad-note-8-TAB", [this,ed]{ padToggle(Pad::NOTE8, ed); }}, { "pad-note-16", [this,ed]{ padToggle(Pad::NOTE16, ed); }}, { "pad-note-16-TAB", [this,ed]{ padToggle(Pad::NOTE16, ed); }}, { "pad-note-32", [this,ed]{ padToggle(Pad::NOTE32, ed); }}, { "pad-note-32-TAB", [this,ed]{ padToggle(Pad::NOTE32, ed); }}, { "pad-note-64", [this,ed]{ padToggle(Pad::NOTE64, ed); }}, { "pad-note-64-TAB", [this,ed]{ padToggle(Pad::NOTE64, ed); }}, { "pad-note-128", [this,ed]{ padToggle(Pad::NOTE128, ed); }}, { "pad-note-128-TAB", [this,ed]{ padToggle(Pad::NOTE128, ed); }}, { "reset-style", [this]{ cmdResetStyle(); }}, { "reset-beammode", [this]{ cmdResetBeamMode(); }}, { "reset-groupings", [this]{ cmdResetNoteAndRestGroupings(); }}, { "clef-violin", [this]{ cmdInsertClef(ClefType::G); }}, { "clef-bass", [this]{ cmdInsertClef(ClefType::F); }}, { "voice-x12", [this]{ cmdExchangeVoice(0, 1); }}, { "voice-x13", [this]{ cmdExchangeVoice(0, 2); }}, { "voice-x14", [this]{ cmdExchangeVoice(0, 3); }}, { "voice-x23", [this]{ cmdExchangeVoice(1, 2); }}, { "voice-x24", [this]{ cmdExchangeVoice(1, 3); }}, { "voice-x34", [this]{ cmdExchangeVoice(2, 3); }}, { "pad-rest", [this,ed]{ padToggle(Pad::REST, ed); }}, { "pad-dot", [this,ed]{ padToggle(Pad::DOT, ed); }}, { "pad-dotdot", [this,ed]{ padToggle(Pad::DOTDOT, ed); }}, { "pad-dot3", [this,ed]{ padToggle(Pad::DOT3, ed); }}, { "pad-dot4", [this,ed]{ padToggle(Pad::DOT4, ed); }}, { "beam-start", [this]{ cmdSetBeamMode(Beam::Mode::BEGIN); }}, { "beam-mid", [this]{ cmdSetBeamMode(Beam::Mode::MID); }}, { "no-beam", [this]{ cmdSetBeamMode(Beam::Mode::NONE); }}, { "beam32", [this]{ cmdSetBeamMode(Beam::Mode::BEGIN32); }}, { "beam64", [this]{ cmdSetBeamMode(Beam::Mode::BEGIN64); }}, { "sharp2", [this,ed]{ toggleAccidental(AccidentalType::SHARP2, ed); }}, { "sharp", [this,ed]{ toggleAccidental(AccidentalType::SHARP, ed); }}, { "nat", [this,ed]{ toggleAccidental(AccidentalType::NATURAL, ed); }}, { "flat", [this,ed]{ toggleAccidental(AccidentalType::FLAT, ed); }}, { "flat2", [this,ed]{ toggleAccidental(AccidentalType::FLAT2, ed); }}, { "flip", [this]{ cmdFlip(); }}, { "stretch+", [this]{ cmdAddStretch(0.1); }}, { "stretch-", [this]{ cmdAddStretch(-0.1); }}, { "pitch-spell", [this]{ spell(); }}, { "select-all", [this]{ cmdSelectAll(); }}, { "select-section", [this]{ cmdSelectSection(); }}, { "add-brackets", [this]{ cmdAddBracket(); }}, { "add-parentheses", [this]{ cmdAddParentheses(); }}, { "acciaccatura", [this]{ cmdAddGrace(NoteType::ACCIACCATURA, MScore::division / 2); }}, { "appoggiatura", [this]{ cmdAddGrace(NoteType::APPOGGIATURA, MScore::division / 2); }}, { "grace4", [this]{ cmdAddGrace(NoteType::GRACE4, MScore::division); }}, { "grace16", [this]{ cmdAddGrace(NoteType::GRACE16, MScore::division / 4); }}, { "grace32", [this]{ cmdAddGrace(NoteType::GRACE32, MScore::division / 8); }}, { "grace8after", [this]{ cmdAddGrace(NoteType::GRACE8_AFTER, MScore::division / 2); }}, { "grace16after", [this]{ cmdAddGrace(NoteType::GRACE16_AFTER, MScore::division / 4); }}, { "grace32after", [this]{ cmdAddGrace(NoteType::GRACE32_AFTER, MScore::division / 8); }}, { "explode", [this]{ cmdExplode(); }}, { "implode", [this]{ cmdImplode(); }}, { "slash-fill", [this]{ cmdSlashFill(); }}, { "slash-rhythm", [this]{ cmdSlashRhythm(); }}, { "resequence-rehearsal-marks", [this]{ cmdResequenceRehearsalMarks(); }}, { "del-empty-measures", [this]{ cmdRemoveEmptyTrailingMeasures(); }}, { "add-audio", [this]{ addAudioTrack(); }}, { "transpose-up", [this]{ transposeSemitone(1); }}, { "transpose-down", [this]{ transposeSemitone(-1); }}, { "delete", [this]{ cmdDeleteSelection(); }}, { "full-measure-rest", [this]{ cmdFullMeasureRest(); }}, { "toggle-insert-mode", [this]{ _is.setInsertMode(!_is.insertMode()); }}, { "pitch-up", [this]{ cmdPitchUp(); }}, { "pitch-down", [this]{ cmdPitchDown(); }}, { "time-delete", [this]{ cmdTimeDelete(); }}, { "pitch-up-octave", [this]{ cmdPitchUpOctave(); }}, { "pitch-down-octave", [this]{ cmdPitchDownOctave(); }}, { "pad-note-increase", [this,ed]{ cmdPadNoteIncreaseTAB(ed); }}, { "pad-note-decrease", [this,ed]{ cmdPadNoteDecreaseTAB(ed); }}, { "pad-note-increase-TAB", [this,ed]{ cmdPadNoteIncreaseTAB(ed); }}, { "pad-note-decrease-TAB", [this,ed]{ cmdPadNoteDecreaseTAB(ed); }}, { "toggle-mmrest", [this]{ cmdToggleMmrest(); }}, { "toggle-hide-empty", [this]{ cmdToggleHideEmpty(); }}, { "set-visible", [this]{ cmdSetVisible(); }}, { "unset-visible", [this]{ cmdUnsetVisible(); }}, { "system-break", [this]{ cmdToggleLayoutBreak(LayoutBreak::Type::LINE); }}, { "page-break", [this]{ cmdToggleLayoutBreak(LayoutBreak::Type::PAGE); }}, { "section-break", [this]{ cmdToggleLayoutBreak(LayoutBreak::Type::SECTION); }}, { "relayout", [this]{ cmdRelayout(); }}, { "toggle-autoplace", [this]{ cmdToggleAutoplace(false); }}, { "autoplace-enabled", [this]{ cmdToggleAutoplace(true); }}, }; for (const auto& c : cmdList) { if (cmd == c.name) { startCmd(); c.cmd(); endCmd(); return; } } qDebug("unknown cmd <%s>", qPrintable(cmd)); } }