MuseScore/libmscore/cmd.cpp

4147 lines
175 KiB
C++
Raw Normal View History

2012-05-26 14:26:10 +02:00
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2002-2013 Werner Schweer
2012-05-26 14:26:10 +02:00
//
// 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 <assert.h>
2018-01-16 13:38:17 +01:00
#include "types.h"
#include "musescoreCore.h"
2012-05-26 14:26:10 +02:00
#include "score.h"
#include "utils.h"
#include "key.h"
#include "clef.h"
#include "navigate.h"
#include "slur.h"
2013-08-22 12:18:14 +02:00
#include "tie.h"
2012-05-26 14:26:10 +02:00
#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"
2012-05-26 14:26:10 +02:00
#include "stafftype.h"
#include "segment.h"
#include "chordlist.h"
#include "mscore.h"
#include "accidental.h"
#include "sequencer.h"
#include "tremolo.h"
#include "rehearsalmark.h"
2016-10-06 12:21:28 +02:00
#include "sym.h"
2012-05-26 14:26:10 +02:00
2013-05-13 18:49:17 +02:00
namespace Ms {
2016-03-02 13:20:19 +01:00
//---------------------------------------------------------
// reset
//---------------------------------------------------------
void CmdState::reset()
{
layoutFlags = LayoutFlag::NO_FLAGS;
_updateMode = UpdateMode::DoNothing;
_startTick = Fraction(-1,1);
_endTick = Fraction(-1,1);
2019-10-24 15:49:23 +02:00
_startStaff = -1;
_endStaff = -1;
_el = nullptr;
_oneElement = true;
_mb = nullptr;
_oneMeasureBase = true;
2019-10-24 15:49:23 +02:00
_locked = false;
2016-03-02 13:20:19 +01:00
}
//---------------------------------------------------------
// setTick
//---------------------------------------------------------
void CmdState::setTick(const Fraction& t)
2016-03-02 13:20:19 +01:00
{
2019-10-24 15:49:23 +02:00
if (_locked)
return;
if (_startTick == Fraction(-1,1) || t < _startTick)
2016-03-02 13:20:19 +01:00
_startTick = t;
if (_endTick == Fraction(-1,1) || t > _endTick)
2016-03-02 13:20:19 +01:00
_endTick = t;
2017-01-05 11:23:47 +01:00
setUpdateMode(UpdateMode::Layout);
2016-03-02 13:20:19 +01:00
}
2019-10-24 15:49:23 +02:00
//---------------------------------------------------------
// setStaff
//---------------------------------------------------------
void CmdState::setStaff(int st)
{
Q_ASSERT(st > -2);
2019-10-24 15:49:23 +02:00
if (_locked || st == -1)
return;
if (_startStaff == -1 || st < _startStaff)
_startStaff = st;
if (_endStaff == -1 || st > _endStaff)
_endStaff = st;
}
//---------------------------------------------------------
// setMeasureBase
//---------------------------------------------------------
void CmdState::setMeasureBase(const MeasureBase* mb)
{
if (!mb || _mb == mb || _locked)
return;
_oneMeasureBase = !_mb;
_mb = mb;
}
2019-10-24 15:49:23 +02:00
//---------------------------------------------------------
// setElement
//---------------------------------------------------------
void CmdState::setElement(const Element* e)
{
if (!e || _el == e || _locked)
return;
_oneElement = !_el;
2019-10-24 15:49:23 +02:00
_el = e;
if (_oneMeasureBase)
setMeasureBase(e->findMeasureBase());
2019-10-24 15:49:23 +02:00
}
2016-03-02 13:20:19 +01:00
//---------------------------------------------------------
// unsetElement
//---------------------------------------------------------
void CmdState::unsetElement(const Element* e)
{
if (_el == e)
_el = nullptr;
if (_mb == e)
_mb = nullptr;
}
//---------------------------------------------------------
// element
//---------------------------------------------------------
const Element* CmdState::element() const
{
if (_oneElement)
return _el;
if (_oneMeasureBase)
return _mb;
return nullptr;
}
//---------------------------------------------------------
2016-03-02 13:20:19 +01:00
// setUpdateMode
//---------------------------------------------------------
2017-02-09 14:57:40 +01:00
void CmdState::_setUpdateMode(UpdateMode m)
{
_updateMode = m;
}
2016-03-02 13:20:19 +01:00
void CmdState::setUpdateMode(UpdateMode m)
{
if (int(m) > int(_updateMode))
2017-02-09 14:57:40 +01:00
_setUpdateMode(m);
2016-03-02 13:20:19 +01:00
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// startCmd
/// Start a GUI command by clearing the redraw area
/// and starting a user-visible undo.
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
void Score::startCmd()
{
if (MScore::debugMode)
qDebug("===startCmd()");
2016-03-02 13:20:19 +01:00
2016-03-18 09:29:16 +01:00
cmdState().reset();
2012-05-26 14:26:10 +02:00
// Start collecting low-level undo operations for a
// user-visible undo action.
2016-03-10 10:41:31 +01:00
if (undoStack()->active()) {
2012-05-26 14:26:10 +02:00
qDebug("Score::startCmd(): cmd already active");
return;
}
undoStack()->beginMacro(this);
2012-05-26 14:26:10 +02:00
}
2016-06-03 10:17:06 +02:00
//---------------------------------------------------------
// undoRedo
//---------------------------------------------------------
2017-06-13 17:23:11 +02:00
void Score::undoRedo(bool undo, EditData* ed)
2016-06-03 10:17:06 +02:00
{
2018-08-03 01:15:56 +02:00
if (readOnly())
return;
2016-06-03 10:17:06 +02:00
cmdState().reset();
if (undo)
2017-06-13 17:23:11 +02:00
undoStack()->undo(ed);
2016-06-03 10:17:06 +02:00
else
2017-06-13 17:23:11 +02:00
undoStack()->redo(ed);
update(false);
masterScore()->setPlaylistDirty(); // TODO: flag all individual operations
2016-06-03 10:17:06 +02:00
updateSelection();
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// 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)
2012-05-26 14:26:10 +02:00
{
2016-03-10 10:41:31 +01:00
if (!undoStack()->active()) {
2016-03-02 13:20:19 +01:00
qDebug("Score::endCmd(): no cmd active");
2016-03-18 09:29:16 +01:00
update();
2012-05-26 14:26:10 +02:00
return;
}
2018-08-03 01:15:56 +02:00
if (readOnly() || MScore::_error != MS_NO_ERROR)
rollback = true;
2012-05-26 14:26:10 +02:00
if (rollback)
2016-03-10 10:41:31 +01:00
undoStack()->current()->unwind();
update(false);
2012-05-26 14:26:10 +02:00
2014-07-31 16:30:04 +02:00
if (MScore::debugMode)
2016-03-10 10:41:31 +01:00
qDebug("===endCmd() %d", undoStack()->current()->childCount());
const bool noUndo = undoStack()->current()->empty(); // nothing to undo?
2016-03-10 10:41:31 +01:00
undoStack()->endMacro(noUndo);
2015-01-30 17:03:51 +01:00
if (dirty()) {
masterScore()->setPlaylistDirty(); // TODO: flag individual operations
masterScore()->setAutosaveDirty(true);
2015-01-30 17:03:51 +01:00
}
2016-05-18 16:32:53 +02:00
MuseScoreCore::mscoreCore->endCmd();
cmdState().reset();
2012-05-26 14:26:10 +02:00
}
2017-02-09 16:07:07 +01:00
#ifndef NDEBUG
2017-02-09 14:57:40 +01:00
//---------------------------------------------------------
// CmdState::dump
//---------------------------------------------------------
void CmdState::dump()
{
qDebug("CmdState: mode %d %d-%d", int(_updateMode), _startTick.ticks(), _endTick.ticks());
2017-02-09 14:57:40 +01:00
// bool _excerptsChanged { false };
// bool _instrumentsChanged { false };
}
#endif
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// update
// layout & update
//---------------------------------------------------------
void Score::update(bool resetCmdState)
2012-05-26 14:26:10 +02:00
{
2017-02-28 14:26:19 +01:00
bool updateAll = false;
2017-01-05 11:23:47 +01:00
for (MasterScore* ms : *movements()) {
CmdState& cs = ms->cmdState();
2017-02-20 11:43:54 +01:00
ms->deletePostponed();
2017-01-05 11:23:47 +01:00
if (cs.layoutRange()) {
for (Score* s : ms->scoreList())
s->doLayoutRange(cs.startTick(), cs.endTick());
2017-02-28 14:26:19 +01:00
updateAll = true;
2017-01-05 11:23:47 +01:00
}
2017-02-28 14:26:19 +01:00
}
for (MasterScore* ms : *movements()) {
CmdState& cs = ms->cmdState();
if (updateAll || cs.updateAll()) {
2017-01-05 11:23:47 +01:00
for (Score* s : scoreList()) {
2017-02-28 14:26:19 +01:00
for (MuseScoreView* v : s->viewer) {
2017-01-05 11:23:47 +01:00
v->updateAll();
2017-02-28 14:26:19 +01:00
}
2017-01-05 11:23:47 +01:00
}
}
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();
2013-06-27 11:02:42 +02:00
}
if (resetCmdState)
cs.reset();
2016-03-18 09:29:16 +01:00
}
if (_selection.isRange())
_selection.updateSelectedElements();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// deletePostponed
//---------------------------------------------------------
2017-02-20 11:43:54 +01:00
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();
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// cmdAddSpanner
// drop VOLTA, OTTAVA, TRILL, PEDAL, DYNAMIC
2017-11-27 09:56:41 +01:00
// HAIRPIN, LET_RING, VIBRATO and TEXTLINE
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
void Score::cmdAddSpanner(Spanner* spanner, const QPointF& pos, bool firstStaffOnly)
2012-05-26 14:26:10 +02:00
{
int staffIdx;
Segment* segment;
MeasureBase* mb = pos2measure(pos, &staffIdx, 0, &segment, 0);
if (firstStaffOnly)
staffIdx = 0;
2015-03-06 13:38:22 +01:00
// ignore if we do not have a measure
2017-01-18 14:16:33 +01:00
if (mb == 0 || mb->type() != ElementType::MEASURE) {
2012-05-26 14:26:10 +02:00
qDebug("cmdAddSpanner: cannot put object here");
delete spanner;
return;
}
2014-08-18 11:33:17 +02:00
// all spanners live in voice 0 (except slurs/ties)
2012-05-26 14:26:10 +02:00
int track = staffIdx == -1 ? -1 : staffIdx * VOICES;
2014-08-18 11:33:17 +02:00
2012-05-26 14:26:10 +02:00
spanner->setTrack(track);
2014-04-23 14:50:31 +02:00
spanner->setTrack2(track);
2012-05-26 14:26:10 +02:00
2014-05-26 20:48:27 +02:00
if (spanner->anchor() == Spanner::Anchor::SEGMENT) {
2013-06-10 11:03:34 +02:00
spanner->setTick(segment->tick());
Fraction lastTick = lastMeasure()->tick() + lastMeasure()->ticks();
Fraction tick2 = qMin(segment->measure()->tick() + segment->measure()->ticks(), lastTick);
2013-07-16 09:03:47 +02:00
spanner->setTick2(tick2);
2012-05-26 14:26:10 +02:00
}
Fix #29986 : Ottavas in TABs - Solution A) Ottavas are not used in TAB staves, as they do not really make sense in TAB's. Yet, currently ottavas: - can be dropped on TAB staves - ottavas added to standard staves are duplicated in TAB staves linked to them Fix: - Ottavas cannot be dropped on TAB staves any longer. Attempting to drop an Ottava on a TAB, results in the action being ignored. - If an `Ottava` is added to a standard staff linked to TAB staves, the standard staff operation is carried over exactly as before, but the Ottava element is not replicated in the TAB linked staff; instead, the actual pitches of corresponding notes in the TAB staff are shifted by the pitchOffset of the Ottava. - If the `Ottava` span is later edited (in the standard staff, of course), the pitches of the corresponding TAB staff notes are updated accordingly. Regarding adding ottavas directly to TAB staves, either linked, there is no difference between Solution A) and Solution B): with both, ottavas **cannot** be added to TAB staves. When TABs are linked to standard staves and ottavas are added to the standard staff, the differences between Solution A) and B) are: - A) does not create **any** `Ottava` element in TABs; B) creates hidden `Ottava` elements in the linked TAB. - A) modifies the TAB note pitches to render the ottava effect; B) does not change the stored pitches, only the fretting. I am not very fond of the hidden `Ottava` trick of Solution B), but that solution seems more in tune with the current code and easier to understand (and maintain). This solution A) seems to me less tricky, but probably less clear to the unaware developer, as each modification of the 'master' `Ottava` has to be cloned into the linked TAB, at the proper place and time with respect to undo/redo machinery. My 'instinctive' preference is for Solution B), but advice is welcome!
2015-02-25 23:59:41 +01:00
else { // Anchor::MEASURE, Anchor::CHORD, Anchor::NOTE
Measure* m = toMeasure(mb);
2012-05-26 14:26:10 +02:00
QRectF b(m->canvasBoundingRect());
if (pos.x() >= (b.x() + b.width() * .5) && m != lastMeasureMM() && m->nextMeasure()->system() == m->system())
2012-05-26 14:26:10 +02:00
m = m->nextMeasure();
2013-06-10 11:03:34 +02:00
spanner->setTick(m->tick());
spanner->setTick2(m->endTick());
2012-05-26 14:26:10 +02:00
}
spanner->eraseSpannerSegments();
2012-05-26 14:26:10 +02:00
undoAddElement(spanner);
select(spanner, SelectType::SINGLE, 0);
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// 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);
2017-01-16 20:51:12 +01:00
#if 0 // TODO
TextLine* tl = toTextLine(spanner);
2016-07-11 09:48:40 +02:00
if (tl) {
2017-01-16 20:51:12 +01:00
StyledPropertyListIdx st;
Text* t;
// begin
t = tl->beginTextElement();
if (t) {
st = t->textStyleType();
2017-01-16 20:51:12 +01:00
if (st >= StyledPropertyListIdx::DEFAULT)
2017-01-05 11:23:47 +01:00
t->textStyle().restyle(MScore::baseStyle().textStyle(st), textStyle(st));
}
// continue
t = tl->continueTextElement();
if (t) {
st = t->textStyleType();
2017-01-16 20:51:12 +01:00
if (st >= StyledPropertyListIdx::DEFAULT)
2017-01-05 11:23:47 +01:00
t->textStyle().restyle(MScore::baseStyle().textStyle(st), textStyle(st));
}
// end
t = tl->endTextElement();
if (t) {
st = t->textStyleType();
2017-01-16 20:51:12 +01:00
if (st >= StyledPropertyListIdx::DEFAULT)
2017-01-05 11:23:47 +01:00
t->textStyle().restyle(MScore::baseStyle().textStyle(st), textStyle(st));
}
}
2017-01-16 20:51:12 +01:00
#endif
2015-10-28 19:43:55 +01:00
undoAddElement(spanner);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// expandVoice
// fills gaps in voice with rests,
// from previous cr (or beginning of measure) to next cr (or end of measure)
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
void Score::expandVoice(Segment* s, int track)
{
2015-08-27 11:33:23 +02:00
if (!s) {
qDebug("expand voice: no segment");
return;
}
2016-08-02 17:00:49 +02:00
if (s->element(track))
2012-05-26 14:26:10 +02:00
return;
// find previous segment with cr in this track
2012-05-26 14:26:10 +02:00
Segment* ps;
2017-03-08 13:12:26 +01:00
for (ps = s; ps; ps = ps->prev(SegmentType::ChordRest)) {
2012-05-26 14:26:10 +02:00
if (ps->element(track))
break;
}
if (ps) {
ChordRest* cr = toChordRest(ps->element(track));
Fraction tick = cr->tick() + cr->actualTicks();
2012-05-26 14:26:10 +02:00
if (tick > s->tick()) {
// previous cr extends past current segment
2012-05-26 14:26:10 +02:00
qDebug("expandVoice: cannot insert element here");
return;
}
2016-08-02 17:00:49 +02:00
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
2017-03-08 13:12:26 +01:00
ps = ps->measure()->undoGetSegment(SegmentType::ChordRest, tick);
}
2012-05-26 14:26:10 +02:00
}
//
// fill up to s->tick() with rests
2012-05-26 14:26:10 +02:00
//
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);
2012-05-26 14:26:10 +02:00
//
// fill from s->tick() until next chord/rest in measure
2012-05-26 14:26:10 +02:00
//
Segment* ns;
2017-03-08 13:12:26 +01:00
for (ns = s->next(SegmentType::ChordRest); ns; ns = ns->next(SegmentType::ChordRest)) {
2012-05-26 14:26:10 +02:00
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);
2012-05-26 14:26:10 +02:00
else
setRest(s->tick(), track, ticks, false, 0);
2012-05-26 14:26:10 +02:00
}
void Score::expandVoice()
{
Segment* s = _is.segment();
int track = _is.track();
expandVoice(s, track);
}
//---------------------------------------------------------
// cmdAddInterval
//---------------------------------------------------------
2016-02-06 22:03:43 +01:00
void Score::cmdAddInterval(int val, const std::vector<Note*>& nl)
2012-05-26 14:26:10 +02:00
{
startCmd();
for (Note* on : nl) {
Note* note = new Note(this);
2012-05-26 14:26:10 +02:00
Chord* chord = on->chord();
note->setParent(chord);
note->setTrack(chord->track());
2012-05-26 14:26:10 +02:00
int valTmp = val < 0 ? val+1 : val-1;
int npitch;
2014-04-23 11:08:51 +02:00
int ntpc1;
int ntpc2;
bool accidental = _is.noteEntryMode() && _is.accidentalType() != AccidentalType::NONE;
bool forceAccidental = false;
if (abs(valTmp) != 7 || accidental) {
int line = on->line() - valTmp;
Fraction tick = chord->tick();
2012-05-26 14:26:10 +02:00
Staff* estaff = staff(on->staffIdx() + chord->staffMove());
ClefType clef = estaff->clef(tick);
2014-06-20 17:07:22 +02:00
Key key = estaff->key(tick);
int ntpc;
if (accidental) {
AccidentalVal acci = Accidental::subtype2value(_is.accidentalType());
int step = absStep(line, clef);
int octave = step / 7;
npitch = step2pitch(step) + octave * 12 + int(acci);
forceAccidental = (npitch == line2pitch(line, clef, key));
ntpc = step2tpc(step % 7, acci);
}
else {
npitch = line2pitch(line, clef, key);
ntpc = pitch2tpc(npitch, key, Prefer::NEAREST);
}
2014-04-23 11:08:51 +02:00
Interval v = on->part()->instrument(tick)->transpose();
2014-04-23 11:08:51 +02:00
if (v.isZero())
ntpc1 = ntpc2 = ntpc;
else {
2018-03-27 15:36:00 +02:00
if (styleB(Sid::concertPitch)) {
2014-04-23 11:08:51 +02:00
v.flip();
ntpc1 = ntpc;
ntpc2 = Ms::transposeTpc(ntpc, v, true);
2014-04-23 11:08:51 +02:00
}
else {
npitch += v.chromatic;
ntpc2 = ntpc;
ntpc1 = Ms::transposeTpc(ntpc, v, true);
2014-04-23 11:08:51 +02:00
}
}
2012-05-26 14:26:10 +02:00
}
else { //special case for octave
Interval interval(7, 12);
if (val < 0)
2012-05-26 14:26:10 +02:00
interval.flip();
2014-04-23 11:08:51 +02:00
transposeInterval(on->pitch(), on->tpc(), &npitch, &ntpc1, interval, false);
ntpc1 = on->tpc1();
ntpc2 = on->tpc2();
2012-05-26 14:26:10 +02:00
}
if (npitch < 0 || npitch > 127) {
delete note;
2016-09-22 12:02:27 +02:00
endCmd();
return;
}
2014-04-23 11:08:51 +02:00
note->setPitch(npitch, ntpc1, ntpc2);
2012-05-26 14:26:10 +02:00
undoAddElement(note);
if (forceAccidental) {
Accidental* a = new Accidental(this);
a->setAccidentalType(_is.accidentalType());
a->setRole(AccidentalRole::USER);
a->setParent(note);
undoAddElement(a);
}
2016-03-18 09:29:16 +01:00
setPlayNote(true);
2012-05-26 14:26:10 +02:00
select(note, SelectType::SINGLE, 0);
2012-05-26 14:26:10 +02:00
}
if (_is.noteEntryMode())
_is.setAccidentalType(AccidentalType::NONE);
2013-10-24 12:09:00 +02:00
_is.moveToNextInputPos();
2012-05-26 14:26:10 +02:00
endCmd();
}
//---------------------------------------------------------
// setGraceNote
/// Create a grace note in front of a normal note.
/// \arg ch is the chord of the normal note
2012-05-26 14:26:10 +02:00
/// \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)
2012-05-26 14:26:10 +02:00
{
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());
2012-05-26 14:26:10 +02:00
chord->setTrack(ch->track());
2013-06-12 14:23:57 +02:00
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();
2012-05-26 14:26:10 +02:00
TDuration d;
d.setVal(len);
chord->setDurationType(d);
chord->setTicks(d.fraction());
2012-05-26 14:26:10 +02:00
chord->setNoteType(type);
2018-03-27 15:36:00 +02:00
chord->setMag(ch->staff()->mag(chord->tick()) * styleD(Sid::graceNoteMag));
2012-05-26 14:26:10 +02:00
2013-06-12 14:23:57 +02:00
undoAddElement(chord);
select(note, SelectType::SINGLE, 0);
return note;
2012-05-26 14:26:10 +02:00
}
2016-08-04 17:29:07 +02:00
//---------------------------------------------------------
// 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)
2016-08-04 17:29:07 +02:00
{
Fraction tick(t);
2016-08-04 17:29:07 +02:00
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);
2016-08-04 17:29:07 +02:00
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);
}
}
2016-08-04 17:29:07 +02:00
tick += ncr->actualTicks();
ocr = ncr;
}
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// 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)
2012-05-26 14:26:10 +02:00
{
2017-03-08 13:12:26 +01:00
Q_ASSERT(segment->segmentType() == SegmentType::ChordRest);
2012-05-26 14:26:10 +02:00
bool isRest = nval.pitch == -1;
Fraction tick = segment->tick();
2012-05-26 14:26:10 +02:00
Element* nr = 0;
Tie* tie = 0;
ChordRest* cr = toChordRest(segment->element(track));
2012-05-26 14:26:10 +02:00
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(),
2012-05-26 14:26:10 +02:00
sd.denominator());
break;
}
measure = segment->measure();
std::vector<TDuration> 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) {
2016-02-06 22:03:43 +01:00
const TDuration& d = dl[i];
2012-05-26 14:26:10 +02:00
ChordRest* ncr;
Note* note = 0;
Tie* addTie = 0;
if (isRest) {
2012-05-26 14:26:10 +02:00
nr = ncr = new Rest(this);
nr->setTrack(track);
ncr->setDurationType(d);
ncr->setTicks(d == TDuration::DurationType::V_MEASURE ? measure->ticks() : d.fraction());
2012-05-26 14:26:10 +02:00
}
else {
nr = note = new Note(this);
if (tie) {
tie->setEndNote(note);
note->setTieBack(tie);
addTie = tie;
2012-05-26 14:26:10 +02:00
}
Chord* chord = new Chord(this);
chord->setTrack(track);
chord->setDurationType(d);
chord->setTicks(d.fraction());
2012-05-26 14:26:10 +02:00
chord->setStemDirection(stemDirection);
chord->add(note);
note->setNval(nval, tick);
if (forceAccidental) {
int tpc = styleB(Sid::concertPitch) ? nval.tpc1 : nval.tpc2;
AccidentalVal alter = tpc2alter(tpc);
AccidentalType at = Accidental::value2subtype(alter);
Accidental* a = new Accidental(this);
a->setAccidentalType(at);
a->setRole(AccidentalRole::USER);
note->add(a);
}
2012-05-26 14:26:10 +02:00
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);
2016-03-18 09:29:16 +01:00
setPlayNote(true);
2012-05-26 14:26:10 +02:00
segment = ncr->segment();
tick += ncr->actualTicks();
}
sd -= dd;
if (sd.isZero())
break;
2017-03-08 13:12:26 +01:00
Segment* nseg = tick2segment(tick, false, SegmentType::ChordRest);
2012-05-26 14:26:10 +02:00
if (nseg == 0) {
qDebug("reached end of score");
break;
}
segment = nseg;
cr = toChordRest(segment->element(track));
2012-05-26 14:26:10 +02:00
if (cr == 0) {
if (track % VOICES)
cr = addRest(segment, track, TDuration(TDuration::DurationType::V_MEASURE), 0);
2012-05-26 14:26:10 +02:00
else {
qDebug("no rest in voice 0");
break;
}
}
//
// Note does not fit on current measure, create Tie to
// next part of note
if (!isRest) {
2012-05-26 14:26:10 +02:00
tie = new Tie(this);
tie->setStartNote((Note*)nr);
tie->setTrack(nr->track());
((Note*)nr)->setTieFor(tie);
}
}
if (tie)
connectTies();
2014-07-14 13:59:54 +02:00
if (nr) {
2017-01-18 14:16:33 +01:00
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();
2014-07-14 13:59:54 +02:00
//
// extend slur
//
Chord* chord = toNote(nr)->chord();
2018-03-27 15:36:00 +02:00
_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<Element*>(ee);
2014-07-29 14:08:52 +02:00
if (e->score() == slur->score() && e->track() == slur->track2()) {
slur->score()->undo(new ChangeSpannerElements(slur, slur->startElement(), e));
break;
}
}
2014-07-14 13:59:54 +02:00
}
}
select(nr, SelectType::SINGLE, 0);
2014-07-14 13:59:54 +02:00
}
2012-05-26 14:26:10 +02:00
return segment;
}
//---------------------------------------------------------
// makeGap
// make time gap at tick by removing/shortening
// chord/rest
//
2013-05-10 10:51:27 +02:00
// if keepChord, the chord at tick is not removed
//
2012-05-26 14:26:10 +02:00
// gap does not exceed measure or scope of tuplet
//
// return size of actual gap
//---------------------------------------------------------
2013-05-10 10:51:27 +02:00
Fraction Score::makeGap(Segment* segment, int track, const Fraction& _sd, Tuplet* tuplet, bool keepChord)
2012-05-26 14:26:10 +02:00
{
Q_ASSERT(_sd.numerator());
2012-05-26 14:26:10 +02:00
Measure* measure = segment->measure();
Fraction accumulated;
2012-05-26 14:26:10 +02:00
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();
2012-05-26 14:26:10 +02:00
2017-03-08 13:12:26 +01:00
for (Segment* seg = firstSegment; seg; seg = seg->next(SegmentType::ChordRest)) {
2012-05-26 14:26:10 +02:00
//
// voices != 0 may have gaps:
//
ChordRest* cr = toChordRest(seg->element(track));
2012-05-26 14:26:10 +02:00
if (!cr) {
if (seg->tick() < nextTick)
continue;
2017-03-08 13:12:26 +01:00
Segment* seg1 = seg->next(SegmentType::ChordRest);
Fraction tick2 = seg1 ? seg1->tick() : seg->measure()->tick() + seg->measure()->ticks();
2016-08-03 17:29:52 +02:00
segment = seg;
Fraction td(tick2 - seg->tick());
2012-05-26 14:26:10 +02:00
if (td > sd)
td = sd;
accumulated += td;
2012-05-26 14:26:10 +02:00
sd -= td;
if (sd.isZero())
break;
nextTick = tick2;
2012-05-26 14:26:10 +02:00
continue;
}
2016-08-03 17:29:52 +02:00
if (seg->tick() > nextTick) {
// there was a gap
Fraction td(seg->tick() - nextTick);
2016-08-03 17:29:52 +02:00
if (td > sd)
td = sd;
accumulated += td;
2016-08-03 17:29:52 +02:00
sd -= td;
if (sd.isZero())
break;
2016-08-03 17:29:52 +02:00
}
2012-05-26 14:26:10 +02:00
//
// 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();
}
2013-06-20 17:23:24 +02:00
if (tupletEnd)
break;
2012-05-26 14:26:10 +02:00
}
Fraction td(cr->ticks());
2012-05-26 14:26:10 +02:00
// remove tremolo between 2 notes, if present
2016-08-03 17:29:52 +02:00
if (cr->isChord()) {
Chord* c = toChord(cr);
if (c->tremolo()) {
Tremolo* tremolo = c->tremolo();
if (tremolo->twoNotes())
undoRemoveElement(tremolo);
}
}
2012-05-26 14:26:10 +02:00
Tuplet* ltuplet = cr->tuplet();
if (ltuplet != tuplet) {
2012-05-26 14:26:10 +02:00
//
// 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
2012-05-26 14:26:10 +02:00
Tuplet* t = ltuplet;
while (t->elements().back()->isTuplet())
t = toTuplet(t->elements().back());
seg = toChordRest(t->elements().back())->segment();
2012-05-26 14:26:10 +02:00
// now delete the full tuplet
td = ltuplet->ticks();
2012-05-26 14:26:10 +02:00
cmdDeleteTuplet(ltuplet, false);
tuplet = 0;
}
else {
if (seg != firstSegment || !keepChord)
2013-05-10 10:51:27 +02:00
undoRemoveElement(cr);
// even if there was a tuplet, we didn't remove it
ltuplet = 0;
2012-05-26 14:26:10 +02:00
}
Fraction timeStretch = cr->staff()->timeStretch(cr->tick());
nextTick += actualTicks(td, tuplet, timeStretch);
2012-05-26 14:26:10 +02:00
if (sd < td) {
//
// we removed too much
//
accumulated = _sd;
2012-05-26 14:26:10 +02:00
Fraction rd = td - sd;
2016-02-06 22:03:43 +01:00
std::vector<TDuration> dList = toDurationList(rd, false);
if (dList.empty())
break;
2012-05-26 14:26:10 +02:00
Fraction tick = cr->tick() + actualTicks(sd, tuplet, timeStretch);
2012-05-26 14:26:10 +02:00
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();
}
2012-05-26 14:26:10 +02:00
}
}
else {
for (size_t i = dList.size(); i > 0; --i) { // loop needs to be in this reverse order
if (ltuplet) {
// take care not to recreate tuplet we just deleted
Rest* r = setRest(tick, track, dList[i-1].fraction(), false, 0, false);
tick += r->actualTicks();
}
else {
tick += addClone(cr, tick, dList[i-1])->actualTicks();
}
}
2012-05-26 14:26:10 +02:00
}
break;
2012-05-26 14:26:10 +02:00
}
accumulated += td;
2012-05-26 14:26:10 +02:00
sd -= td;
if (sd.isZero())
break;
2012-05-26 14:26:10 +02:00
}
// Fraction ticks = measure->tick() + measure->ticks() - segment->tick();
2012-05-26 14:26:10 +02:00
// Fraction td = Fraction::fromTicks(ticks);
// NEEDS REVIEW !!
// once the statement below is removed, these two lines do nothing
// if (td > sd)
// td = sd;
// ??? accumulated should already contain the total value of the created gap: line 749, 811 or 838
2012-05-26 14:26:10 +02:00
// 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
// accumulated += td;
const Fraction t1 = firstSegmentEnd;
const Fraction t2 = firstSegment->tick() + accumulated;
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<Sel>(int(Sel::ALL) & ~int(Sel::CHORD_SYMBOL));
deleteAnnotationsFromRange(s1, s2, track, track + 1, filter);
deleteSpannersFromRange(t1, t2, track, track + 1, filter);
}
return accumulated;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// makeGap1
2014-07-31 08:37:58 +02:00
// make time gap for each voice
// starting at tick+voiceOffset[voice] by removing/shortening
2012-05-26 14:26:10 +02:00
// 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])
2012-05-26 14:26:10 +02:00
{
2017-03-08 13:12:26 +01:00
Segment* seg = tick2segment(baseTick, true, SegmentType::ChordRest);
2012-05-26 14:26:10 +02:00
if (!seg) {
qDebug("no segment to paste at tick %d", baseTick.ticks());
2012-05-26 14:26:10 +02:00
return false;
}
2014-07-27 11:15:46 +02:00
int strack = staffIdx * VOICES;
2014-07-31 08:37:58 +02:00
for (int track = strack; track < strack + VOICES; track++) {
if (voiceOffset[track-strack] == -1)
continue;
Fraction tick = baseTick + Fraction::fromTicks(voiceOffset[track-strack]);
2014-07-31 08:37:58 +02:00
Measure* m = tick2measure(tick);
if ((track % VOICES) && !m->hasVoices(staffIdx))
continue;
2014-07-31 08:37:58 +02:00
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);
2014-07-31 08:37:58 +02:00
bool result = makeGapVoice(seg, track, newLen, tick);
if (track == strack && !result) // makeGap failed for first voice
2014-07-27 11:15:46 +02:00
return false;
}
return true;
}
bool Score::makeGapVoice(Segment* seg, int track, Fraction len, const Fraction& tick)
2014-07-27 11:15:46 +02:00
{
ChordRest* cr = 0;
cr = toChordRest(seg->element(track));
2012-05-26 14:26:10 +02:00
if (!cr) {
2013-06-12 14:23:57 +02:00
// check if we are in the middle of a chord/rest
2017-03-08 13:12:26 +01:00
Segment* seg1 = seg->prev(SegmentType::ChordRest);
2013-06-12 14:23:57 +02:00
for (;;) {
if (seg1 == 0) {
if (!(track % VOICES))
qDebug("no segment before tick %d", tick.ticks());
2014-07-31 08:37:58 +02:00
// this happens only for voices other than voice 1
expandVoice(seg, track);
return makeGapVoice(seg,track,len,tick);
2012-05-26 14:26:10 +02:00
}
2013-06-12 14:23:57 +02:00
if (seg1->element(track))
break;
2017-03-08 13:12:26 +01:00
seg1 = seg1->prev(SegmentType::ChordRest);
2012-05-26 14:26:10 +02:00
}
ChordRest* cr1 = toChordRest(seg1->element(track));
Fraction srcF = cr1->ticks();
Fraction dstF = tick - cr1->tick();
2016-02-06 22:03:43 +01:00
std::vector<TDuration> 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);
2017-01-18 14:16:33 +01:00
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();
}
}
}
2013-06-20 15:25:11 +02:00
setRest(tick, track, srcF - dstF, true, 0);
2013-06-12 14:23:57 +02:00
for (;;) {
2017-03-08 13:12:26 +01:00
seg1 = seg1->next1(SegmentType::ChordRest);
2013-06-20 15:25:11 +02:00
if (seg1 == 0) {
qDebug("no segment");
2013-06-12 14:23:57 +02:00
return false;
2012-05-26 14:26:10 +02:00
}
2013-06-20 15:25:11 +02:00
if (seg1->element(track)) {
cr = toChordRest(seg1->element(track));
2013-06-12 14:23:57 +02:00
break;
2012-05-26 14:26:10 +02:00
}
}
}
for (;;) {
if (!cr) {
qDebug("cannot make gap");
2012-05-26 14:26:10 +02:00
return false;
}
Fraction l = makeGap(cr->segment(), cr->track(), len, 0);
if (l.isZero()) {
qDebug("returns zero gap");
2012-05-26 14:26:10 +02:00
return false;
}
len -= l;
if (len.isZero())
break;
// go to next cr
Measure* m = cr->measure()->nextMeasure();
if (m == 0) {
qDebug("EOS reached");
2017-01-18 14:16:33 +01:00
insertMeasure(ElementType::MEASURE, 0, false);
2012-05-26 14:26:10 +02:00
m = cr->measure()->nextMeasure();
if (m == 0) {
qDebug("===EOS reached");
return true;
}
}
2014-07-31 08:37:58 +02:00
// first segment in measure was removed, have to recreate it
2017-03-08 13:12:26 +01:00
Segment* s = m->undoGetSegment(SegmentType::ChordRest, m->tick());
int t = cr->track();
cr = toChordRest(s->element(t));
2012-05-26 14:26:10 +02:00
if (cr == 0) {
addRest(s, t, TDuration(TDuration::DurationType::V_MEASURE), 0);
cr = toChordRest(s->element(t));
2012-05-26 14:26:10 +02:00
}
}
return true;
}
//---------------------------------------------------------
// splitGapToMeasureBoundaries
// cr - start of gap
// gap - gap len
//---------------------------------------------------------
QList<Fraction> Score::splitGapToMeasureBoundaries(ChordRest* cr, Fraction gap)
{
QList<Fraction> flist;
Tuplet* tuplet = cr->tuplet();
if (tuplet) {
Fraction rest = tuplet->elementsDuration();
for (DurationElement* de : tuplet->elements()) {
if (de == cr)
break;
rest -= de->ticks();
}
2012-05-26 14:26:10 +02:00
if (rest < gap)
qDebug("does not fit in tuplet");
else
flist.append(gap);
return flist;
}
Segment* s = cr->segment();
while (gap > Fraction(0,1)) {
2012-05-26 14:26:10 +02:00
Measure* m = s->measure();
Fraction timeStretch = cr->staff()->timeStretch(s->tick());
Fraction rest = (m->ticks() - s->rtick()) * timeStretch;
2012-05-26 14:26:10 +02:00
if (rest >= gap) {
flist.append(gap);
return flist;
}
flist.append(rest);
gap -= rest;
m = m->nextMeasure();
if (m == 0)
return flist;
2017-03-08 13:12:26 +01:00
s = m->first(SegmentType::ChordRest);
2012-05-26 14:26:10 +02:00
}
return flist;
}
//---------------------------------------------------------
// changeCRlen
//---------------------------------------------------------
void Score::changeCRlen(ChordRest* cr, const TDuration& d)
{
Fraction dstF;
if (d.type() == TDuration::DurationType::V_MEASURE)
2012-05-26 14:26:10 +02:00
dstF = cr->measure()->stretchedLen(cr->staff());
else
dstF = d.fraction();
2016-08-03 17:29:52 +02:00
changeCRlen(cr, dstF);
}
2012-05-26 14:26:10 +02:00
2016-08-04 17:29:07 +02:00
void Score::changeCRlen(ChordRest* cr, const Fraction& dstF, bool fillWithRest)
2016-08-03 17:29:52 +02:00
{
if (cr->isRepeatMeasure()) {
// it is not clear what should this
// operation mean for measure repeats.
return;
}
Fraction srcF(cr->ticks());
if (srcF == dstF) {
if (cr->isFullMeasureRest())
undoChangeChordRestLen(cr, dstF);
2012-05-26 14:26:10 +02:00
return;
}
2014-06-19 12:54:13 +02:00
//keep selected element if any
Element* selElement = selection().isSingle() ? getSelectedElement() : 0;
int track = cr->track();
2012-05-26 14:26:10 +02:00
Tuplet* tuplet = cr->tuplet();
2012-05-26 14:26:10 +02:00
if (srcF > dstF) {
//
// make shorter and fill with rest
//
2013-05-10 10:51:27 +02:00
deselectAll();
2016-08-03 17:29:52 +02:00
if (cr->isChord()) {
2012-05-26 14:26:10 +02:00
//
// remove ties and tremolo between 2 notes
2012-05-26 14:26:10 +02:00
//
Chord* c = toChord(cr);
if (c->tremolo()) {
Tremolo* tremolo = c->tremolo();
if (tremolo->twoNotes())
undoRemoveElement(tremolo);
}
2014-06-19 12:54:13 +02:00
foreach (Note* n, c->notes()) {
2012-05-26 14:26:10 +02:00
if (n->tieFor())
undoRemoveElement(n->tieFor());
}
}
Fraction timeStretch = cr->staff()->timeStretch(cr->tick());
2016-08-04 17:29:07 +02:00
std::vector<TDuration> dList = toDurationList(dstF, true);
undoChangeChordRestLen(cr, dList[0]);
Fraction tick2 = cr->tick();
2016-08-04 17:29:07 +02:00
for (unsigned i = 1; i < dList.size(); ++i) {
tick2 += actualTicks(dList[i-1].ticks(), tuplet, timeStretch);
2016-08-04 17:29:07 +02:00
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);
2014-06-19 12:54:13 +02:00
if (selElement)
select(selElement, SelectType::SINGLE, 0);
2012-05-26 14:26:10 +02:00
return;
}
//
// make longer
//
// split required len into Measures
QList<Fraction> flist = splitGapToMeasureBoundaries(cr, dstF);
2016-02-06 22:03:43 +01:00
if (flist.empty())
2012-05-26 14:26:10 +02:00
return;
2013-05-10 10:51:27 +02:00
deselectAll();
2012-05-26 14:26:10 +02:00
Fraction tick = cr->tick();
2012-05-26 14:26:10 +02:00
Fraction f = dstF;
ChordRest* cr1 = cr;
Chord* oc = 0;
bool first = true;
for (Fraction f2 : flist) {
2012-05-26 14:26:10 +02:00
f -= f2;
2013-05-10 10:51:27 +02:00
makeGap(cr1->segment(), cr1->track(), f2, tuplet, first);
2012-05-26 14:26:10 +02:00
2016-08-03 17:29:52 +02:00
if (cr->isRest()) {
2012-05-26 14:26:10 +02:00
Fraction timeStretch = cr1->staff()->timeStretch(cr1->tick());
Rest* r = toRest(cr);
if (first) {
2016-02-06 22:03:43 +01:00
std::vector<TDuration> dList = toDurationList(f2, true);
undoChangeChordRestLen(cr, dList[0]);
Fraction tick2 = cr->tick();
2016-02-06 22:03:43 +01:00
for (unsigned i = 1; i < dList.size(); ++i) {
tick2 += actualTicks(dList[i-1].ticks(), tuplet, timeStretch);
TDuration d = dList[i];
setRest(tick2, track, d.fraction(), (d.dots() > 0), tuplet);
}
}
else {
r = setRest(tick, track, f2, false, tuplet);
}
2012-05-26 14:26:10 +02:00
if (first) {
select(r, SelectType::SINGLE, 0);
2012-05-26 14:26:10 +02:00
first = false;
}
tick += actualTicks(f2, tuplet, timeStretch);
2012-05-26 14:26:10 +02:00
}
else {
2016-02-06 22:03:43 +01:00
std::vector<TDuration> dList = toDurationList(f2, true);
2016-08-03 17:29:52 +02:00
Measure* measure = tick2measure(tick);
Fraction etick = measure->tick();
2016-08-03 17:29:52 +02:00
if (((tick - etick).ticks() % dList[0].ticks().ticks()) == 0) {
2016-08-03 17:29:52 +02:00
for (TDuration du : dList) {
2012-05-26 14:26:10 +02:00
bool genTie;
Chord* cc;
if (oc) {
genTie = true;
cc = oc;
2013-05-10 10:51:27 +02:00
oc = addChord(tick, du, cc, genTie, tuplet);
2012-05-26 14:26:10 +02:00
}
else {
genTie = false;
cc = toChord(cr);
2013-05-10 10:51:27 +02:00
undoChangeChordRestLen(cr, du);
oc = cc;
2012-05-26 14:26:10 +02:00
}
if (oc && first) {
2014-06-19 12:54:13 +02:00
if (!selElement)
select(oc, SelectType::SINGLE, 0);
else
2014-06-19 12:54:13 +02:00
select(selElement, SelectType::SINGLE, 0);
2012-05-26 14:26:10 +02:00
first = false;
}
if (oc)
tick += oc->actualTicks();
}
}
else {
for (size_t i = dList.size(); i > 0; --i) { // loop probably needs to be in this reverse order
2012-05-26 14:26:10 +02:00
bool genTie;
Chord* cc;
if (oc) {
genTie = true;
cc = oc;
oc = addChord(tick, dList[i-1], cc, genTie, tuplet);
2012-05-26 14:26:10 +02:00
}
else {
genTie = false;
cc = toChord(cr);
undoChangeChordRestLen(cr, dList[i-1]);
2013-05-10 10:51:27 +02:00
oc = cc;
2012-05-26 14:26:10 +02:00
}
if (first) {
2014-11-22 13:05:23 +01:00
// select(oc, SelectType::SINGLE, 0);
if (selElement)
select(selElement, SelectType::SINGLE, 0);
2012-05-26 14:26:10 +02:00
first = false;
}
tick += oc->actualTicks();
}
}
}
Measure* m = cr1->measure();
Measure* m1 = m->nextMeasure();
if (m1 == 0)
break;
2017-03-08 13:12:26 +01:00
Segment* s = m1->first(SegmentType::ChordRest);
2012-05-26 14:26:10 +02:00
expandVoice(s, track);
cr1 = toChordRest(s->element(track));
2012-05-26 14:26:10 +02:00
}
connectTies();
}
//---------------------------------------------------------
// upDownChromatic
//---------------------------------------------------------
2014-06-20 17:07:22 +02:00
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()) {
2014-06-20 17:07:22 +02:00
if (tpc1 > Tpc::TPC_A + int(key))
newTpc1 = tpc1 - 5; // up semitone diatonic
else
newTpc1 = tpc1 + 7; // up semitone chromatic
2014-04-14 10:39:27 +02:00
newTpc2 = n->transposeTpc(newTpc1);
}
else {
2014-06-20 17:07:22 +02:00
if (tpc2 > Tpc::TPC_A + int(key))
newTpc2 = tpc2 - 5; // up semitone diatonic
else
newTpc2 = tpc2 + 7; // up semitone chromatic
2014-04-14 10:39:27 +02:00
newTpc1 = n->transposeTpc(newTpc2);
}
}
else if (!up && pitch > 0) {
newPitch = pitch - 1;
if (n->concertPitch()) {
2014-06-20 17:07:22 +02:00
if (tpc1 > Tpc::TPC_C + int(key))
newTpc1 = tpc1 - 7; // down semitone chromatic
else
newTpc1 = tpc1 + 5; // down semitone diatonic
2014-04-14 10:39:27 +02:00
newTpc2 = n->transposeTpc(newTpc1);
}
else {
2014-06-20 17:07:22 +02:00
if (tpc2 > Tpc::TPC_C + int(key))
newTpc2 = tpc2 - 7; // down semitone chromatic
else
newTpc2 = tpc2 + 5; // down semitone diatonic
2014-04-14 10:39:27 +02:00
newTpc1 = n->transposeTpc(newTpc2);
}
}
}
//---------------------------------------------------------
// setTpc
//---------------------------------------------------------
2014-04-14 10:39:27 +02:00
static void setTpc(Note* oNote, int tpc, int& newTpc1, int& newTpc2)
{
if (oNote->concertPitch()) {
newTpc1 = tpc;
2014-04-14 10:39:27 +02:00
newTpc2 = oNote->transposeTpc(tpc);
}
else {
newTpc2 = tpc;
2014-04-14 10:39:27 +02:00
newTpc1 = oNote->transposeTpc(tpc);
}
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// upDown
/// Increment/decrement pitch of note by one or by an octave.
//---------------------------------------------------------
void Score::upDown(bool up, UpDownMode mode)
2012-05-26 14:26:10 +02:00
{
2014-05-26 12:10:59 +02:00
QList<Note*> el = selection().uniqueNotes();
2012-05-26 14:26:10 +02:00
2016-03-02 13:20:19 +01:00
for (Note* oNote : el) {
Fraction tick = oNote->chord()->tick();
Staff* staff = oNote->staff();
Part* part = staff->part();
Key key = staff->key(tick);
2014-04-10 13:13:37 +02:00
int tpc1 = oNote->tpc1();
int tpc2 = oNote->tpc2();
2013-06-12 14:23:57 +02:00
int pitch = oNote->pitch();
2014-04-10 13:13:37 +02:00
int newTpc1 = tpc1; // default to unchanged
int newTpc2 = tpc2; // default to unchanged
int newPitch = pitch; // default to unchanged
2013-06-12 14:23:57 +02:00
int string = oNote->string();
int fret = oNote->fret();
2012-05-26 14:26:10 +02:00
2016-12-13 13:16:17 +01:00
switch (staff->staffType(oNote->chord()->tick())->group()) {
case StaffGroup::PERCUSSION:
2012-05-26 14:26:10 +02:00
{
const Drumset* ds = part->instrument()->drumset();
if (ds) {
fix #273601: Updated UI for pianoroll editor. Updating main piano roll display to have 12 rows per octave. Updating piano keyboard to track main piano roll window. Moving PianoKeyboard to its own file. Better tracking of playhead during playback. Drawing beat lines within measure. Scrollbars now adjusted for stasis when zooming in the pianoview. Piano ruler now showing tick marks respecting zoom level. Fixing scroll issue during playback. Taking direct control of selection. PianoItem is no longer a Qt object. Preping for note group drag. Can now drag pitch changes. Adjusting viewport pos when staff set. Removing CMakeLists.txt Adding CMakeLists.txt back in. Cleaning things up for resubmission. Now displaying drumset names in PianoKeyboard when relevant. Updating tracking signal in Pianoroll Editor. Cleanup. Locator lines no longer Qt objects. Removing redundant note move on mouse up. Now adjusting final event for playback. Now showing note voice name. Using Tpc to use note name tha reflects accidentals. Better checking for Position when opening Pianoroll window. Adding note for ctrl-click on rest. Swithing velocity type will now also adjust to equivilent velocity value. Better checking for single note selection. Pianoroll window. Adding note for ctrl-click on rest. equivilent velocity value. Better checking for single note selection. Ctrl-click now adds note to selection. Note up/down now correct direction for drumroll Fixing calcuation for switching note velocity. Can now horiz resize piano keyboard. Setting up new PianoLevels control for setting note event values. Now drawing subbeat divisions. Can now cut notes. User can now select data type to display. Can now use mouse to adjust levels. Adding undo to note event changes. Now smoother transition between absolute and offset level values. Smoother update communication between controls. Adding labels to piano levels. Removing unneccary blocking of scene() in pianoview. Now handles adjusting level with 1px wide mouse sweep. mouse sweep. 'c8199ac5a'. fix #273601: Updated UI for pianoroll editor. Now handling setting null staff. Adding missing .h files to CMakeLists.txt Removing undo step to avoid selection problem. Adding tool tip display. Keyboard display now adjusting to reflect transposition of instrument. Now showing part name in Piano Roll editor. Moving color display options into preferences. Levels editor now using preferences too. Fixing incorrent note being highlighted in piano roll editor. Base color now used for coloring levels panel. Cleaning code Investigating cut/pasete actions. Adding popup menu. Encapsulating selection into a command. Creating minimal popup menu. Can now insert notes of arbitrary duraition to piano roll. Adding dark and light color schemes to piano roll editor. Can now set subbeats to non powers of 2. Fixing bad rebase. Holding shift will clamp values to mousedown position. Adding dropdown to modify which white lines are shown behind notes.
2018-10-21 10:27:23 +02:00
newPitch = up ? ds->nextPitch(pitch) : ds->prevPitch(pitch);
newTpc1 = pitch2tpc(newPitch, Key::C, Prefer::NEAREST);
newTpc2 = newTpc1;
}
2012-05-26 14:26:10 +02:00
}
break;
case StaffGroup::TAB:
2012-05-26 14:26:10 +02:00
{
const StringData* stringData = part->instrument()->stringData();
2014-05-13 17:15:32 +02:00
switch (mode) {
case UpDownMode::OCTAVE: // move same note to next string, if possible
2012-05-26 14:26:10 +02:00
{
const StaffType* stt = staff->staffType(tick);
string = stt->physStringToVisual(string);
2012-05-26 14:26:10 +02:00
string += (up ? -1 : 1);
2014-03-26 11:02:23 +01:00
if (string < 0 || string >= stringData->strings())
2012-05-26 14:26:10 +02:00
return; // no next string to move to
string = stt->visualStringToPhys(string);
fret = stringData->fret(pitch, string, staff, tick);
2014-03-26 11:02:23 +01:00
if (fret == -1) // can't have that note on that string
2012-05-26 14:26:10 +02:00
return;
// newPitch and newTpc remain unchanged
2012-05-26 14:26:10 +02:00
}
break;
case UpDownMode::DIATONIC: // increase / decrease the pitch,
2012-05-26 14:26:10 +02:00
// letting the algorithm to choose fret & string
upDownChromatic(up, pitch, oNote, key, tpc1, tpc2, newPitch, newTpc1, newTpc2);
2012-05-26 14:26:10 +02:00
break;
case UpDownMode::CHROMATIC: // increase / decrease the fret
2012-05-26 14:26:10 +02:00
{ // without changing the string
// compute new fret
if (!stringData->frets()) {
2014-05-26 12:10:59 +02:00
qDebug("upDown tab chromatic: no frets?");
return;
}
2012-05-26 14:26:10 +02:00
fret += (up ? 1 : -1);
if (fret < 0 || fret > stringData->frets()) {
qDebug("upDown tab in-string: out of fret range");
return;
2014-05-26 12:10:59 +02:00
}
// 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;
}
2012-05-26 14:26:10 +02:00
// store the fretting change before undoChangePitch() chooses
// a fretting of its own liking!
2018-03-27 15:36:00 +02:00
oNote->undoChangeProperty(Pid::FRET, fret);
2012-05-26 14:26:10 +02:00
}
break;
}
}
break;
case StaffGroup::STANDARD:
2016-03-02 13:20:19 +01:00
switch (mode) {
case UpDownMode::OCTAVE:
if (up) {
if (pitch < 116)
newPitch = pitch + 12;
}
else {
if (pitch > 11)
newPitch = pitch - 12;
}
// newTpc remains unchanged
2012-05-26 14:26:10 +02:00
break;
case UpDownMode::CHROMATIC:
upDownChromatic(up, pitch, oNote, key, tpc1, tpc2, newPitch, newTpc1, newTpc2);
2012-05-26 14:26:10 +02:00
break;
case UpDownMode::DIATONIC:
{
int tpc = oNote->tpc();
if (up) {
2014-06-20 17:07:22 +02:00
if (tpc > Tpc::TPC_A + int(key)) {
if (pitch < 127) {
newPitch = pitch + 1;
2014-04-14 10:39:27 +02:00
setTpc(oNote, tpc - 5, newTpc1, newTpc2);
}
}
else {
if (pitch < 126) {
newPitch = pitch + 2;
2014-04-14 10:39:27 +02:00
setTpc(oNote, tpc + 2, newTpc1, newTpc2);
}
}
}
else {
2014-06-20 17:07:22 +02:00
if (tpc > Tpc::TPC_C + int(key)) {
if (pitch > 1) {
newPitch = pitch - 2;
2014-04-14 10:39:27 +02:00
setTpc(oNote, tpc - 2, newTpc1, newTpc2);
}
}
else {
if (pitch > 0) {
newPitch = pitch - 1;
2014-04-14 10:39:27 +02:00
setTpc(oNote, tpc + 5, newTpc1, newTpc2);
}
}
}
}
2012-05-26 14:26:10 +02:00
break;
}
break;
}
2014-04-10 13:13:37 +02:00
if ((oNote->pitch() != newPitch) || (oNote->tpc1() != newTpc1) || oNote->tpc2() != newTpc2) {
// remove accidental if present to make sure
// user added accidentals are removed here
// unless it's an octave change
// in this case courtesy accidentals are preserved
// because they're now harder to be re-entered due to the revised note-input workflow
if (mode != UpDownMode::OCTAVE) {
auto l = oNote->linkList();
for (ScoreElement* e : l) {
Note* ln = toNote(e);
if (ln->accidental())
undo(new RemoveElement(ln->accidental()));
}
}
2014-04-10 13:13:37 +02:00
undoChangePitch(oNote, newPitch, newTpc1, newTpc2);
}
2012-05-26 14:26:10 +02:00
// store fret change only if undoChangePitch has not been called,
// as undoChangePitch() already manages fret changes, if necessary
2016-12-13 13:16:17 +01:00
else if (staff->staffType(tick)->group() == StaffGroup::TAB) {
bool refret = false;
if (oNote->string() != string) {
2018-03-27 15:36:00 +02:00
oNote->undoChangeProperty(Pid::STRING, string);
refret = true;
}
if (oNote->fret() != fret) {
2018-03-27 15:36:00 +02:00
oNote->undoChangeProperty(Pid::FRET, fret);
refret = true;
}
2014-03-26 11:02:23 +01:00
if (refret) {
const StringData* stringData = part->instrument()->stringData();
stringData->fretChords(oNote->chord());
2014-03-26 11:02:23 +01:00
}
2012-08-06 09:29:11 +02:00
}
2012-05-26 14:26:10 +02:00
// play new note with velocity 80 for 0.3 sec:
2016-03-18 09:29:16 +01:00
setPlayNote(true);
2012-05-26 14:26:10 +02:00
}
setSelectionChanged(true);
fix #273601: Updated UI for pianoroll editor. Updating main piano roll display to have 12 rows per octave. Updating piano keyboard to track main piano roll window. Moving PianoKeyboard to its own file. Better tracking of playhead during playback. Drawing beat lines within measure. Scrollbars now adjusted for stasis when zooming in the pianoview. Piano ruler now showing tick marks respecting zoom level. Fixing scroll issue during playback. Taking direct control of selection. PianoItem is no longer a Qt object. Preping for note group drag. Can now drag pitch changes. Adjusting viewport pos when staff set. Removing CMakeLists.txt Adding CMakeLists.txt back in. Cleaning things up for resubmission. Now displaying drumset names in PianoKeyboard when relevant. Updating tracking signal in Pianoroll Editor. Cleanup. Locator lines no longer Qt objects. Removing redundant note move on mouse up. Now adjusting final event for playback. Now showing note voice name. Using Tpc to use note name tha reflects accidentals. Better checking for Position when opening Pianoroll window. Adding note for ctrl-click on rest. Swithing velocity type will now also adjust to equivilent velocity value. Better checking for single note selection. Pianoroll window. Adding note for ctrl-click on rest. equivilent velocity value. Better checking for single note selection. Ctrl-click now adds note to selection. Note up/down now correct direction for drumroll Fixing calcuation for switching note velocity. Can now horiz resize piano keyboard. Setting up new PianoLevels control for setting note event values. Now drawing subbeat divisions. Can now cut notes. User can now select data type to display. Can now use mouse to adjust levels. Adding undo to note event changes. Now smoother transition between absolute and offset level values. Smoother update communication between controls. Adding labels to piano levels. Removing unneccary blocking of scene() in pianoview. Now handles adjusting level with 1px wide mouse sweep. mouse sweep. 'c8199ac5a'. fix #273601: Updated UI for pianoroll editor. Now handling setting null staff. Adding missing .h files to CMakeLists.txt Removing undo step to avoid selection problem. Adding tool tip display. Keyboard display now adjusting to reflect transposition of instrument. Now showing part name in Piano Roll editor. Moving color display options into preferences. Levels editor now using preferences too. Fixing incorrent note being highlighted in piano roll editor. Base color now used for coloring levels panel. Cleaning code Investigating cut/pasete actions. Adding popup menu. Encapsulating selection into a command. Creating minimal popup menu. Can now insert notes of arbitrary duraition to piano roll. Adding dark and light color schemes to piano roll editor. Can now set subbeats to non powers of 2. Fixing bad rebase. Holding shift will clamp values to mousedown position. Adding dropdown to modify which white lines are shown behind notes.
2018-10-21 10:27:23 +02:00
}
//---------------------------------------------------------
// upDownDelta
/// Add the delta to the pitch of note.
//---------------------------------------------------------
void Score::upDownDelta(int pitchDelta)
fix #273601: Updated UI for pianoroll editor. Updating main piano roll display to have 12 rows per octave. Updating piano keyboard to track main piano roll window. Moving PianoKeyboard to its own file. Better tracking of playhead during playback. Drawing beat lines within measure. Scrollbars now adjusted for stasis when zooming in the pianoview. Piano ruler now showing tick marks respecting zoom level. Fixing scroll issue during playback. Taking direct control of selection. PianoItem is no longer a Qt object. Preping for note group drag. Can now drag pitch changes. Adjusting viewport pos when staff set. Removing CMakeLists.txt Adding CMakeLists.txt back in. Cleaning things up for resubmission. Now displaying drumset names in PianoKeyboard when relevant. Updating tracking signal in Pianoroll Editor. Cleanup. Locator lines no longer Qt objects. Removing redundant note move on mouse up. Now adjusting final event for playback. Now showing note voice name. Using Tpc to use note name tha reflects accidentals. Better checking for Position when opening Pianoroll window. Adding note for ctrl-click on rest. Swithing velocity type will now also adjust to equivilent velocity value. Better checking for single note selection. Pianoroll window. Adding note for ctrl-click on rest. equivilent velocity value. Better checking for single note selection. Ctrl-click now adds note to selection. Note up/down now correct direction for drumroll Fixing calcuation for switching note velocity. Can now horiz resize piano keyboard. Setting up new PianoLevels control for setting note event values. Now drawing subbeat divisions. Can now cut notes. User can now select data type to display. Can now use mouse to adjust levels. Adding undo to note event changes. Now smoother transition between absolute and offset level values. Smoother update communication between controls. Adding labels to piano levels. Removing unneccary blocking of scene() in pianoview. Now handles adjusting level with 1px wide mouse sweep. mouse sweep. 'c8199ac5a'. fix #273601: Updated UI for pianoroll editor. Now handling setting null staff. Adding missing .h files to CMakeLists.txt Removing undo step to avoid selection problem. Adding tool tip display. Keyboard display now adjusting to reflect transposition of instrument. Now showing part name in Piano Roll editor. Moving color display options into preferences. Levels editor now using preferences too. Fixing incorrent note being highlighted in piano roll editor. Base color now used for coloring levels panel. Cleaning code Investigating cut/pasete actions. Adding popup menu. Encapsulating selection into a command. Creating minimal popup menu. Can now insert notes of arbitrary duraition to piano roll. Adding dark and light color schemes to piano roll editor. Can now set subbeats to non powers of 2. Fixing bad rebase. Holding shift will clamp values to mousedown position. Adding dropdown to modify which white lines are shown behind notes.
2018-10-21 10:27:23 +02:00
{
while (pitchDelta > 0) {
upDown(true, UpDownMode::CHROMATIC);
fix #273601: Updated UI for pianoroll editor. Updating main piano roll display to have 12 rows per octave. Updating piano keyboard to track main piano roll window. Moving PianoKeyboard to its own file. Better tracking of playhead during playback. Drawing beat lines within measure. Scrollbars now adjusted for stasis when zooming in the pianoview. Piano ruler now showing tick marks respecting zoom level. Fixing scroll issue during playback. Taking direct control of selection. PianoItem is no longer a Qt object. Preping for note group drag. Can now drag pitch changes. Adjusting viewport pos when staff set. Removing CMakeLists.txt Adding CMakeLists.txt back in. Cleaning things up for resubmission. Now displaying drumset names in PianoKeyboard when relevant. Updating tracking signal in Pianoroll Editor. Cleanup. Locator lines no longer Qt objects. Removing redundant note move on mouse up. Now adjusting final event for playback. Now showing note voice name. Using Tpc to use note name tha reflects accidentals. Better checking for Position when opening Pianoroll window. Adding note for ctrl-click on rest. Swithing velocity type will now also adjust to equivilent velocity value. Better checking for single note selection. Pianoroll window. Adding note for ctrl-click on rest. equivilent velocity value. Better checking for single note selection. Ctrl-click now adds note to selection. Note up/down now correct direction for drumroll Fixing calcuation for switching note velocity. Can now horiz resize piano keyboard. Setting up new PianoLevels control for setting note event values. Now drawing subbeat divisions. Can now cut notes. User can now select data type to display. Can now use mouse to adjust levels. Adding undo to note event changes. Now smoother transition between absolute and offset level values. Smoother update communication between controls. Adding labels to piano levels. Removing unneccary blocking of scene() in pianoview. Now handles adjusting level with 1px wide mouse sweep. mouse sweep. 'c8199ac5a'. fix #273601: Updated UI for pianoroll editor. Now handling setting null staff. Adding missing .h files to CMakeLists.txt Removing undo step to avoid selection problem. Adding tool tip display. Keyboard display now adjusting to reflect transposition of instrument. Now showing part name in Piano Roll editor. Moving color display options into preferences. Levels editor now using preferences too. Fixing incorrent note being highlighted in piano roll editor. Base color now used for coloring levels panel. Cleaning code Investigating cut/pasete actions. Adding popup menu. Encapsulating selection into a command. Creating minimal popup menu. Can now insert notes of arbitrary duraition to piano roll. Adding dark and light color schemes to piano roll editor. Can now set subbeats to non powers of 2. Fixing bad rebase. Holding shift will clamp values to mousedown position. Adding dropdown to modify which white lines are shown behind notes.
2018-10-21 10:27:23 +02:00
pitchDelta--;
}
while (pitchDelta < 0) {
upDown(false, UpDownMode::CHROMATIC);
fix #273601: Updated UI for pianoroll editor. Updating main piano roll display to have 12 rows per octave. Updating piano keyboard to track main piano roll window. Moving PianoKeyboard to its own file. Better tracking of playhead during playback. Drawing beat lines within measure. Scrollbars now adjusted for stasis when zooming in the pianoview. Piano ruler now showing tick marks respecting zoom level. Fixing scroll issue during playback. Taking direct control of selection. PianoItem is no longer a Qt object. Preping for note group drag. Can now drag pitch changes. Adjusting viewport pos when staff set. Removing CMakeLists.txt Adding CMakeLists.txt back in. Cleaning things up for resubmission. Now displaying drumset names in PianoKeyboard when relevant. Updating tracking signal in Pianoroll Editor. Cleanup. Locator lines no longer Qt objects. Removing redundant note move on mouse up. Now adjusting final event for playback. Now showing note voice name. Using Tpc to use note name tha reflects accidentals. Better checking for Position when opening Pianoroll window. Adding note for ctrl-click on rest. Swithing velocity type will now also adjust to equivilent velocity value. Better checking for single note selection. Pianoroll window. Adding note for ctrl-click on rest. equivilent velocity value. Better checking for single note selection. Ctrl-click now adds note to selection. Note up/down now correct direction for drumroll Fixing calcuation for switching note velocity. Can now horiz resize piano keyboard. Setting up new PianoLevels control for setting note event values. Now drawing subbeat divisions. Can now cut notes. User can now select data type to display. Can now use mouse to adjust levels. Adding undo to note event changes. Now smoother transition between absolute and offset level values. Smoother update communication between controls. Adding labels to piano levels. Removing unneccary blocking of scene() in pianoview. Now handles adjusting level with 1px wide mouse sweep. mouse sweep. 'c8199ac5a'. fix #273601: Updated UI for pianoroll editor. Now handling setting null staff. Adding missing .h files to CMakeLists.txt Removing undo step to avoid selection problem. Adding tool tip display. Keyboard display now adjusting to reflect transposition of instrument. Now showing part name in Piano Roll editor. Moving color display options into preferences. Levels editor now using preferences too. Fixing incorrent note being highlighted in piano roll editor. Base color now used for coloring levels panel. Cleaning code Investigating cut/pasete actions. Adding popup menu. Encapsulating selection into a command. Creating minimal popup menu. Can now insert notes of arbitrary duraition to piano roll. Adding dark and light color schemes to piano roll editor. Can now set subbeats to non powers of 2. Fixing bad rebase. Holding shift will clamp values to mousedown position. Adding dropdown to modify which white lines are shown behind notes.
2018-10-21 10:27:23 +02:00
pitchDelta++;
}
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// addArticulation
/// Add attribute \a attr to all selected notes/rests.
///
/// Called from padToggle() to add note prefix/accent.
//---------------------------------------------------------
2016-10-06 12:21:28 +02:00
void Score::addArticulation(SymId attr)
2012-05-26 14:26:10 +02:00
{
QSet<Chord*> set;
2016-10-06 12:21:28 +02:00
for (Element* el : selection().elements()) {
if (el->isNote() || el->isChord()) {
Chord* cr = 0;
// apply articulation on a given chord only once
2016-10-06 12:21:28 +02:00
if (el->isNote()) {
cr = toNote(el)->chord();
if (set.contains(cr))
continue;
}
2012-05-26 14:26:10 +02:00
Articulation* na = new Articulation(this);
2016-10-06 12:21:28 +02:00
na->setSymId(attr);
if (!addArticulation(el, na))
delete na;
if (cr)
set.insert(cr);
2012-05-26 14:26:10 +02:00
}
}
}
//---------------------------------------------------------
// 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);
}
}
//---------------------------------------------------------
2012-05-26 14:26:10 +02:00
// changeAccidental
/// Change accidental to subtype \a idx for all selected
/// notes.
//---------------------------------------------------------
2015-04-02 10:33:53 +02:00
void Score::changeAccidental(AccidentalType idx)
2012-05-26 14:26:10 +02:00
{
2013-11-27 11:51:16 +01:00
foreach(Note* note, selection().noteList())
2012-05-26 14:26:10 +02:00
changeAccidental(note, idx);
}
//---------------------------------------------------------
// changeAccidental2
//---------------------------------------------------------
2013-11-27 11:51:16 +01:00
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();
2016-12-13 13:16:17 +01:00
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);
}
}
2014-04-14 10:39:27 +02:00
int tpc1;
int tpc2 = n->transposeTpc(tpc);
2018-03-27 15:36:00 +02:00
if (score->styleB(Sid::concertPitch))
2014-04-14 10:39:27 +02:00
tpc1 = tpc;
else {
tpc1 = tpc2;
tpc2 = tpc;
}
2016-12-13 13:16:17 +01:00
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();
2014-05-05 14:02:26 +02:00
score->undo(new ChangePitch(nn, pitch, tpc1, tpc2));
}
}
}
score->undoChangePitch(n, pitch, tpc1, tpc2);
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// changeAccidental
/// Change accidental to subtype \accidental for
/// note \a note.
//---------------------------------------------------------
2015-04-02 10:33:53 +02:00
void Score::changeAccidental(Note* note, AccidentalType accidental)
2012-05-26 14:26:10 +02:00
{
Chord* chord = note->chord();
if (!chord)
return;
2012-05-26 14:26:10 +02:00
Segment* segment = chord->segment();
if (!segment)
return;
2012-05-26 14:26:10 +02:00
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();
2012-05-26 14:26:10 +02:00
while (step < 0)
step += 7;
step %= 7;
//
// accidental change may result in pitch change
//
2014-04-14 10:39:27 +02:00
AccidentalVal acc2 = measure->findAccidental(note);
2015-04-02 10:33:53 +02:00
AccidentalVal acc = (accidental == AccidentalType::NONE) ? acc2 : Accidental::subtype2value(accidental);
2012-05-26 14:26:10 +02:00
2014-06-20 17:07:22 +02:00
int pitch = line2pitch(note->line(), clef, Key::C) + int(acc);
2014-04-14 10:39:27 +02:00
if (!note->concertPitch())
pitch += note->transposition();
2014-05-05 14:02:26 +02:00
2014-04-14 10:39:27 +02:00
int tpc = step2tpc(step, acc);
bool forceRemove = false;
bool forceAdd = false;
// delete accidental
// both for this note and for any linked notes
2015-04-02 10:33:53 +02:00
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(note->accidentalType())) || Accidental::isMicrotonal(accidental))
forceAdd = true;
2012-05-26 14:26:10 +02:00
for (ScoreElement* se : note->linkList()) {
2017-12-20 16:49:30 +01:00
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);
}
else if (a && Accidental::isMicrotonal(a->accidentalType())) {
lns->undoRemoveElement(a);
}
changeAccidental2(ln, pitch, tpc);
}
setPlayNote(true);
setSelectionChanged(true);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// addArticulation
//---------------------------------------------------------
bool Score::addArticulation(Element* el, Articulation* a)
2012-05-26 14:26:10 +02:00
{
2018-01-17 13:25:23 +01:00
Chord* c;
2016-10-06 12:21:28 +02:00
if (el->isNote())
2018-01-17 13:25:23 +01:00
c = toNote(el)->chord();
else if (el->isChord())
c = toChord(el);
else
return false;
2018-01-17 13:25:23 +01:00
Articulation* oa = c->hasArticulation(a);
2012-05-26 14:26:10 +02:00
if (oa) {
undoRemoveElement(oa);
return false;
2012-05-26 14:26:10 +02:00
}
2018-01-17 13:25:23 +01:00
a->setParent(c);
a->setTrack(c->track()); // make sure it propagates between score and parts
undoAddElement(a);
return true;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// 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
2015-05-19 02:12:37 +02:00
if (!s1)
m1 = firstMeasureMM();
2012-05-26 14:26:10 +02:00
else
m1 = s1->measure();
2015-05-19 02:12:37 +02:00
if (!s2)
m2 = lastMeasureMM();
2012-05-26 14:26:10 +02:00
else
m2 = s2->measure();
2015-05-19 02:12:37 +02:00
if (!m1 || !m2) // should not happen!
2012-05-26 14:26:10 +02:00
return;
2015-05-19 02:12:37 +02:00
for (Measure* m = m1; m; m = m->nextMeasureMM()) {
2018-03-27 15:36:00 +02:00
m->undoChangeProperty(Pid::USER_STRETCH, 1.0);
2012-05-26 14:26:10 +02:00
if (m == m2)
break;
}
}
//---------------------------------------------------------
// moveUp
//---------------------------------------------------------
void Score::moveUp(ChordRest* cr)
2012-05-26 14:26:10 +02:00
{
Staff* staff = cr->staff();
Part* part = staff->part();
int rstaff = staff->rstaff();
int staffMove = cr->staffMove();
2012-05-26 14:26:10 +02:00
if ((staffMove == -1) || (rstaff + staffMove <= 0))
return;
QList<Staff*>* staves = part->staves();
// we know that staffMove+rstaff-1 index exists due to the previous condition.
2016-12-13 13:16:17 +01:00
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));
}
}
2016-12-13 13:16:17 +01:00
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// moveDown
//---------------------------------------------------------
void Score::moveDown(ChordRest* cr)
2012-05-26 14:26:10 +02:00
{
Staff* staff = cr->staff();
2012-05-26 14:26:10 +02:00
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();
2012-05-26 14:26:10 +02:00
if ((staffMove == 1) || (rstaff + staffMove >= rstaves - 1)) {
qDebug("moveDown staffMove==%d rstaff %d rstaves %d", staffMove, rstaff, rstaves);
2012-05-26 14:26:10 +02:00
return;
}
QList<Staff*>* staves = part->staves();
// we know that staffMove+rstaff+1 index exists due to the previous condition.
2016-12-13 13:16:17 +01:00
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));
}
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// cmdAddStretch
//---------------------------------------------------------
void Score::cmdAddStretch(qreal val)
{
2014-05-24 12:53:50 +02:00
if (!selection().isRange())
2012-05-26 14:26:10 +02:00
return;
Fraction startTick = selection().tickStart();
Fraction endTick = selection().tickEnd();
2014-04-16 11:41:06 +02:00
for (Measure* m = firstMeasureMM(); m; m = m->nextMeasureMM()) {
2012-05-26 14:26:10 +02:00
if (m->tick() < startTick)
continue;
if (m->tick() >= endTick)
break;
qreal stretch = m->userStretch();
stretch += val;
if (stretch < 0)
stretch = 0;
2018-03-27 15:36:00 +02:00
m->undoChangeProperty(Pid::USER_STRETCH, stretch);
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// cmdResetBeamMode
//---------------------------------------------------------
void Score::cmdResetBeamMode()
{
bool noSelection = selection().isNone();
if (noSelection)
cmdSelectAll();
else if (!selection().isRange()) {
2012-05-26 14:26:10 +02:00
qDebug("no system or staff selected");
return;
}
Fraction endTick = selection().tickEnd();
2012-05-26 14:26:10 +02:00
2017-03-08 13:12:26 +01:00
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));
2012-05-26 14:26:10 +02:00
if (cr == 0)
continue;
2017-01-18 14:16:33 +01:00
if (cr->type() == ElementType::CHORD) {
if (cr->beamMode() != Beam::Mode::AUTO)
2018-03-27 15:36:00 +02:00
cr->undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::AUTO));
2012-05-26 14:26:10 +02:00
}
2017-01-18 14:16:33 +01:00
else if (cr->type() == ElementType::REST) {
if (cr->beamMode() != Beam::Mode::NONE)
2018-03-27 15:36:00 +02:00
cr->undoChangeProperty(Pid::BEAM_MODE, int(Beam::Mode::NONE));
2012-05-26 14:26:10 +02:00
}
}
}
if (noSelection)
deselectAll();
2012-05-26 14:26:10 +02:00
}
2018-10-29 11:03:23 +01:00
//---------------------------------------------------------
// 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();
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// processMidiInput
//---------------------------------------------------------
bool Score::processMidiInput()
{
if (midiInputQueue()->empty())
2012-05-26 14:26:10 +02:00
return false;
if (MScore::debugMode)
qDebug("processMidiInput");
2016-08-08 17:46:37 +02:00
NoteEntryMethod entryMethod = _is.noteEntryMethod();
2012-05-26 14:26:10 +02:00
bool cmdActive = false;
while (!midiInputQueue()->empty()) {
MidiInputEvent ev = midiInputQueue()->dequeue();
2016-08-08 17:46:37 +02:00
for (auto itr = activeMidiPitches()->begin(); itr != activeMidiPitches()->end();) {
if ((*itr).pitch == ev.pitch)
itr = activeMidiPitches()->erase(itr);
else
++itr;
}
2012-05-26 14:26:10 +02:00
if (MScore::debugMode)
qDebug("<-- !noteentry dequeue %i", ev.pitch);
2016-08-08 17:46:37 +02:00
if (!noteEntryMode()
|| entryMethod == NoteEntryMethod::REALTIME_AUTO
|| entryMethod == NoteEntryMethod::REALTIME_MANUAL) {
2012-05-26 14:26:10 +02:00
int staffIdx = selection().staffStart();
Part* p;
if (staffIdx < 0 || staffIdx >= nstaves())
p = staff(0)->part();
2012-05-26 14:26:10 +02:00
else
p = staff(staffIdx)->part();
if (p) {
2018-03-27 15:36:00 +02:00
if (!styleB(Sid::concertPitch)) {
ev.pitch += p->instrument(selection().tickStart())->transpose().chromatic;
}
MScore::seq->startNote(
fix #275313: rework mixer ui 2 Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Setting color in collapsed mode now affects all channels. Using shared_ptr to track MixerTrackItem. Part changes now affect all instruments. Creating new track UI object to handle parts. Using shard_ptr to track MixerTrackItem objects. setting port and channel data now. Changing to horizontal layout. Fixing knob display. Chaning track control appearance. Setting init slider window size. Switchong back to vertical orientation. Fixing a few UI bugs in the slider. Tracks now left aligned. Moving details panel above mixer. Now changing track selection when user clicks on sliders. Pan and volume controls now reflect track color. Showing volume and pan values in tooltips. Creating a new slider control for mixer. Switching Channel's volume, pan, reverb and chorus and chaning them to doubles with a decimal range. No longer writing out vol, pan, chor, reverb when at default values. Nolonger writing vol, pan, chorus, reverb as controler values in output file. Now testing against default values on write. More export fixes. Manually editing test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. More test changes to make Travis happy. More test changes to make Travis happy. Importing MusicXML now matches new volume, pan ranges. Changing range of pan. Fixing a few bugs with calculating MIDI. Altering test files for Travis. fix #275313: rework-mixer-ui-2 Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Moving PartEditBase into separate file. Creating new files for building mixer. Creating art assets/UI design for new components. Styling the track control. Adding track area. Separating out score from update. Creating instances of mixer UI. Creating part per voice now. Can click on tracks to select them now. Can now switch bwtewwn tracks. Setting patch channel now. Setting enabled off when no track selected. Improving slider ui. Turning Channel into a class and adding listener to it. Somewhat stabalized sharing track objects between interfaces. Can now apply volume changes to both expanded and collapsed tracks. Pan knob is now working. Encapsulating the rest of the fields in Channel. Mute and solo now working. Reverb and chorus now working. Drumkit checkbox now working. Port and channel somewhat working. Adding support for colors per track. Part name change now working. Separating out MixerTrackItem Finishing moving MixerTrackItem to new file. Cleaning up code. Setting color in collapsed mode now affects all channels. Using shared_ptr to track MixerTrackItem. Part changes now affect all instruments. Creating new track UI object to handle parts. Using shard_ptr to track MixerTrackItem objects. setting port and channel data now. Changing to horizontal layout. Fixing knob display. Chaning track control appearance. Setting init slider window size. Switchong back to vertical orientation. Fixing a few UI bugs in the slider. Tracks now left aligned. Moving details panel above mixer. Now changing track selection when user clicks on sliders. Pan and volume controls now reflect track color. Showing volume and pan values in tooltips. Creating a new slider control for mixer. Switching Channel's volume, pan, reverb and chorus and chaning them to doubles with a decimal range. No longer writing out vol, pan, chor, reverb when at default values. Nolonger writing vol, pan, chorus, reverb as controler values in output file. Now testing against default values on write. More export fixes. Manually editing test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. Manually editing more test files to reflect new channel parameters. More test changes to make Travis happy. More test changes to make Travis happy. Importing MusicXML now matches new volume, pan ranges. Changing range of pan. Fixing a few bugs with calculating MIDI. Altering test files for Travis. Restoring the volume, pan, chorus, reverb to original char data type & range. UI now shows different 'user friendly' ranges. Overwriting tests with versions from master. mtest/libmscore/compat114/clef_missing_first-ref.mscx mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml mtest/musicxml/io/testUninitializedDivisions_ref.xml Restoring test files to original state. Restoring test files to original state. Restoring old values for importing files. Restoring part methods. mtest/importmidi/simplify_8th_dotted_no_staccato.mscx mtest/libmscore/compat114/clef_missing_first-ref.mscx mtest/libmscore/compat114/hor_frame_and_mmrest-ref.mscx mtest/musicxml/io/testInstrumentChangeMIDIportExport_ref.xml mtest/musicxml/io/testUninitializedDivisions_ref.xml Rearranging UI components for better feel. Improving UI. Fixed crash when changing part name. Adding support for two lighting modes. Showing part name over channel expansion. Adding master gain control to mixer. Changing color of gain slider. Adapting to latest source in main. Changing master gain slider to use decibel calculation. CSS now set on tracks whenever a Paint event received. Restoring mixer slider values to refect MIDI ranges. Fixing crash when drumkit checked. Fixing crash when closing score. Fixing alignment in mixer details. Tweaking UI for better appearance.
2018-11-13 18:43:19 +01:00
p->instrument()->channel(0)->channel(),
ev.pitch,
ev.velocity,
0.0);
}
2012-05-26 14:26:10 +02:00
}
2016-08-08 17:46:37 +02:00
if (noteEntryMode()) {
if (ev.velocity == 0) {
// delete note in realtime mode
2017-12-20 16:49:30 +01:00
//Chord* chord = toChord(_is.cr());
2016-08-08 17:46:37 +02:00
//std::vector<Note*> notes = chord->notes();
if (entryMethod == NoteEntryMethod::REALTIME_AUTO || entryMethod == NoteEntryMethod::REALTIME_MANUAL) {
if (_is.cr()->isChord()) {
2017-12-20 16:49:30 +01:00
Note* n = toChord(_is.cr())->findNote(ev.pitch);
2016-08-08 17:46:37 +02:00
if (n) {
qDebug("Pitches match! Note %i, Pitch %i", n->pitch(), ev.pitch);
if (!cmdActive) {
startCmd();
cmdActive = true;
}
deleteItem(n->tieBack());
deleteItem(n);
2016-08-08 17:46:37 +02:00
}
}
}
continue;
2016-08-08 17:46:37 +02:00
}
2012-05-26 14:26:10 +02:00
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;
}
2016-08-08 17:46:37 +02:00
// 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);
2012-05-26 14:26:10 +02:00
}
}
if (cmdActive) {
endCmd();
//after relayout
2014-06-26 11:41:18 +02:00
Element* e = inputState().cr();
if (e) {
for(MuseScoreView* v : viewer)
v->adjustCanvasPosition(e, false);
2013-01-17 20:42:44 +01:00
}
2012-05-26 14:26:10 +02:00
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())
2012-05-26 14:26:10 +02:00
cr = selection().activeCR();
else
cr = selection().lastChordRest();
// no chord/rest found? look for another type of element
if (cr == 0) {
2016-02-06 22:03:43 +01:00
if (selection().elements().empty())
return 0;
2014-07-30 11:32:46 +02:00
// 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()) {
2017-01-18 14:16:33 +01:00
case ElementType::NOTE: // a note is a valid target
trg = el;
cr = toNote(el)->chord();
break;
2017-01-18 14:16:33 +01:00
case ElementType::CHORD: // a chord or a rest are valid targets
case ElementType::REST:
trg = el;
cr = toChordRest(trg);
break;
2017-01-18 14:16:33 +01:00
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()) {
2017-03-08 13:12:26 +01:00
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
2015-08-18 23:00:07 +02:00
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
2017-01-18 14:16:33 +01:00
if (trg->type() == ElementType::CHORD)
trg = toChord(trg)->upNote();
2016-03-18 09:29:16 +01:00
setPlayNote(true);
select(trg, SelectType::SINGLE, 0);
return trg;
}
// if no chordrest found, do nothing
2013-08-02 17:39:45 +02:00
if (cr == 0)
return 0;
// if some chordrest found, continue with default processing
}
2012-05-26 14:26:10 +02:00
Element* el = 0;
2015-08-18 23:00:07 +02:00
Segment* ois = noteEntryMode() ? _is.segment() : nullptr;
Measure* oim = ois ? ois->measure() : nullptr;
2012-05-26 14:26:10 +02:00
if (cmd == "next-chord") {
2015-08-18 23:00:07 +02:00
// note input cursor
2012-05-26 14:26:10 +02:00
if (noteEntryMode())
2013-10-24 12:09:00 +02:00
_is.moveToNextInputPos();
2015-08-18 23:00:07 +02:00
// selection "cursor"
// find next chordrest, which might be a grace note
// this may override note input cursor
2013-06-12 14:23:57 +02:00
el = nextChordRest(cr);
while (el && el->isRest() && toRest(el)->isGap())
el = nextChordRest(toChordRest(el));
2015-08-18 23:00:07 +02:00
if (el && noteEntryMode()) {
// do not use if not in original or new measure (don't skip measures)
Measure* m = toChordRest(el)->measure();
2015-08-18 23:00:07 +02:00
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;
2012-05-26 14:26:10 +02:00
}
else if (cmd == "prev-chord") {
2015-08-18 23:00:07 +02:00
// note input cursor
2014-08-12 16:13:12 +02:00
if (noteEntryMode() && _is.segment()) {
Measure* m = _is.segment()->measure();
2017-03-08 13:12:26 +01:00
Segment* s = _is.segment()->prev1(SegmentType::ChordRest);
2015-08-18 23:00:07 +02:00
int track = _is.track();
2017-03-08 13:12:26 +01:00
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;
}
2012-05-26 14:26:10 +02:00
break;
}
2012-05-26 14:26:10 +02:00
}
2013-10-24 12:09:00 +02:00
_is.moveInputPos(s);
2012-05-26 14:26:10 +02:00
}
2015-08-18 23:00:07 +02:00
// 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));
2015-08-18 23:00:07 +02:00
if (el && noteEntryMode()) {
// do not use if not in original or new measure (don't skip measures)
Measure* m = toChordRest(el)->measure();
2015-08-18 23:00:07 +02:00
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;
2012-05-26 14:26:10 +02:00
}
else if (cmd == "next-measure") {
2012-05-26 14:26:10 +02:00
el = nextMeasure(cr);
2013-10-24 12:09:00 +02:00
if (noteEntryMode())
_is.moveInputPos(el);
2012-05-26 14:26:10 +02:00
}
else if (cmd == "prev-measure") {
2012-05-26 14:26:10 +02:00
el = prevMeasure(cr);
2013-10-24 12:09:00 +02:00
if (noteEntryMode())
_is.moveInputPos(el);
2012-05-26 14:26:10 +02:00
}
else if (cmd == "next-track") {
el = nextTrack(cr);
2013-10-24 12:09:00 +02:00
if (noteEntryMode())
_is.moveInputPos(el);
}
else if (cmd == "prev-track") {
el = prevTrack(cr);
2013-10-24 12:09:00 +02:00
if (noteEntryMode())
_is.moveInputPos(el);
}
2015-08-18 23:00:07 +02:00
2012-05-26 14:26:10 +02:00
if (el) {
2017-01-18 14:16:33 +01:00
if (el->type() == ElementType::CHORD)
el = toChord(el)->upNote(); // originally downNote
2016-03-18 09:29:16 +01:00
setPlayNote(true);
2014-08-17 00:28:47 +02:00
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
2016-03-18 09:29:16 +01:00
setPlayNote(false);
for (MuseScoreView* view : viewer)
2014-08-17 00:28:47 +02:00
view->moveCursor();
}
else {
select(el, SelectType::SINGLE, 0);
}
2012-05-26 14:26:10 +02:00
}
return el;
}
//---------------------------------------------------------
// selectMove
//---------------------------------------------------------
Element* Score::selectMove(const QString& cmd)
{
ChordRest* cr;
if (selection().activeCR())
cr = selection().activeCR();
else
cr = selection().lastChordRest();
2013-10-24 12:09:00 +02:00
if (cr == 0 && noteEntryMode())
2012-05-26 14:26:10 +02:00
cr = inputState().cr();
if (cr == 0)
return 0;
ChordRest* el = 0;
if (cmd == "select-next-chord")
el = nextChordRest(cr, true);
2012-05-26 14:26:10 +02:00
else if (cmd == "select-prev-chord")
el = prevChordRest(cr, true);
2012-05-26 14:26:10 +02:00
else if (cmd == "select-next-measure")
2014-11-25 16:53:43 +01:00
el = nextMeasure(cr, true, true);
2012-05-26 14:26:10 +02:00
else if (cmd == "select-prev-measure")
2014-11-25 16:53:43 +01:00
el = prevMeasure(cr, true);
2012-05-26 14:26:10 +02:00
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();
2012-05-26 14:26:10 +02:00
if (!measure)
return 0;
el = measure->first()->nextChordRest(cr->track());
}
else if (cmd == "select-end-score") {
Measure* measure = lastMeasureMM();
2012-05-26 14:26:10 +02:00
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());
2012-05-26 14:26:10 +02:00
return el;
}
//---------------------------------------------------------
// cmdMirrorNoteHead
//---------------------------------------------------------
void Score::cmdMirrorNoteHead()
{
const QList<Element*>& el = selection().elements();
for (Element* e : el) {
if (e->isNote()) {
Note* note = toNote(e);
2016-12-13 13:16:17 +01:00
if (note->staff() && note->staff()->isTabStaff(note->chord()->tick()))
2018-03-27 15:36:00 +02:00
e->undoChangeProperty(Pid::GHOST, !note->ghost());
2012-05-26 14:26:10 +02:00
else {
MScore::DirectionH d = note->userMirror();
if (d == MScore::DirectionH::AUTO)
d = note->chord()->up() ? MScore::DirectionH::RIGHT : MScore::DirectionH::LEFT;
2012-05-26 14:26:10 +02:00
else
d = d == MScore::DirectionH::LEFT ? MScore::DirectionH::RIGHT : MScore::DirectionH::LEFT;
2012-05-26 14:26:10 +02:00
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));
}
2012-05-26 14:26:10 +02:00
}
}
//---------------------------------------------------------
// 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
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
void Score::cmdIncDecDuration(int nSteps, bool stepDotted)
2012-05-26 14:26:10 +02:00
{
Element* el = selection().element();
if (el == 0)
return;
if (el->isNote())
2012-05-26 14:26:10 +02:00
el = el->parent();
if (!el->isChordRest())
return;
ChordRest* cr = toChordRest(el);
2012-05-26 14:26:10 +02:00
// 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())
2012-05-26 14:26:10 +02:00
return;
if (cr->isChord() && (toChord(cr)->noteType() != NoteType::NORMAL)) {
2012-05-26 14:26:10 +02:00
//
// handle appoggiatura and acciaccatura
//
undoChangeChordRestLen(cr, d);
2012-05-26 14:26:10 +02:00
}
else
changeCRlen(cr, d);
_is.setDuration(d);
nextInputPos(cr, false);
}
//---------------------------------------------------------
// cmdAddBracket
//---------------------------------------------------------
2014-07-25 17:13:27 +02:00
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()) {
2017-01-18 14:16:33 +01:00
if (el->type() == ElementType::NOTE) {
Note* n = toNote(el);
2017-01-31 19:30:49 +01:00
n->addParentheses();
}
2017-01-18 14:16:33 +01:00
else if (el->type() == ElementType::ACCIDENTAL) {
Accidental* acc = toAccidental(el);
2018-03-27 15:36:00 +02:00
acc->undoChangeProperty(Pid::ACCIDENTAL_BRACKET, int(AccidentalBracket::PARENTHESIS));
}
2017-01-18 14:16:33 +01:00
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);
}
}
}
2014-07-25 17:13:27 +02:00
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// cmdMoveRest
//---------------------------------------------------------
2016-03-02 13:20:19 +01:00
void Score::cmdMoveRest(Rest* rest, Direction dir)
2012-05-26 14:26:10 +02:00
{
QPointF pos(rest->offset());
2016-03-02 13:20:19 +01:00
if (dir == Direction::UP)
2012-05-26 14:26:10 +02:00
pos.ry() -= spatium();
2016-03-02 13:20:19 +01:00
else if (dir == Direction::DOWN)
2012-05-26 14:26:10 +02:00
pos.ry() += spatium();
rest->undoChangeProperty(Pid::OFFSET, pos);
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// cmdMoveLyrics
//---------------------------------------------------------
2016-03-02 13:20:19 +01:00
void Score::cmdMoveLyrics(Lyrics* lyrics, Direction dir)
2012-05-26 14:26:10 +02:00
{
2018-07-19 13:03:25 +02:00
int verse = lyrics->no() + (dir == Direction::UP ? -1 : 1);
if (verse < 0)
return;
lyrics->undoChangeProperty(Pid::VERSE, verse);
2012-05-26 14:26:10 +02:00
}
2014-08-06 10:15:58 +02:00
//---------------------------------------------------------
// cmdInsertClef
//---------------------------------------------------------
void Score::cmdInsertClef(ClefType type)
{
if (!noteEntryMode())
return;
undoChangeClef(staff(inputTrack()/VOICES), inputState().cr(), type);
2014-08-06 10:15:58 +02:00
}
2014-07-25 17:13:27 +02:00
//---------------------------------------------------------
// cmdInsertClef
// insert clef before cr
//---------------------------------------------------------
void Score::cmdInsertClef(Clef* clef, ChordRest* cr)
{
undoChangeClef(cr->staff(), cr, clef->clefType());
2014-07-25 17:13:27 +02:00
delete clef;
}
//---------------------------------------------------------
// cmdAddGrace
/// adds grace note of specified type to selected notes
//---------------------------------------------------------
2016-12-06 20:32:08 +01:00
void Score::cmdAddGrace (NoteType graceType, int duration)
{
const QList<Element*> copyOfElements = selection().elements();
for (Element* e : copyOfElements) {
2017-01-18 14:16:33 +01:00
if (e->type() == ElementType::NOTE) {
Note* n = toNote(e);
setGraceNote(n->chord(), n->pitch(), graceType, duration);
}
}
}
2014-11-09 02:02:37 +01:00
//---------------------------------------------------------
// 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();
2016-07-27 23:30:51 +02:00
Fraction lTick = endMeasure->endTick();
2016-07-27 23:30:51 +02:00
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);
2017-01-18 14:16:33 +01:00
if (e && e->type() == ElementType::CHORD) {
2016-07-27 23:30:51 +02:00
Chord* c = toChord(e);
n = qMax(n, int(c->notes().size()));
}
}
lastStaff = qMin(nstaves(), srcStaff + n);
}
const QByteArray mimeData(selection().mimeData());
2016-07-27 23:30:51 +02:00
// 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);
2016-07-27 23:30:51 +02:00
e.setPasteMode(true);
2016-12-01 16:56:10 +01:00
pasteStaff(e, cr->segment(), cr->staffIdx());
2014-11-09 02:02:37 +01:00
}
}
2016-07-27 23:30:51 +02:00
// 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);
2017-01-18 14:16:33 +01:00
if (e && e->type() == ElementType::CHORD) {
2016-07-27 23:30:51 +02:00
Chord* c = toChord(e);
std::vector<Note*> notes = c->notes();
int nnotes = int(notes.size());
2016-07-27 23:30:51 +02:00
// 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);
}
2014-11-09 02:02:37 +01:00
}
}
}
}
2016-07-27 23:30:51 +02:00
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++) {
2016-07-27 23:30:51 +02:00
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));
}
}
2014-11-09 02:02:37 +01:00
// 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
2014-11-09 02:02:37 +01:00
//---------------------------------------------------------
void Score::cmdImplode()
{
if (!selection().isRange())
return;
2016-07-27 23:43:46 +02:00
int dstStaff = selection().staffStart();
int endStaff = selection().staffEnd();
int dstTrack = dstStaff * VOICES;
int startTrack = dstStaff * VOICES;
2016-07-27 23:43:46 +02:00
int endTrack = endStaff * VOICES;
2014-11-09 02:02:37 +01:00
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);
2014-11-09 02:02:37 +01:00
2016-07-27 23:43:46 +02:00
// 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;
}
2014-11-09 02:02:37 +01:00
}
2016-07-27 23:43:46 +02:00
// 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())
2014-11-09 02:02:37 +01:00
continue;
2016-07-27 23:43:46 +02:00
// 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);
}
2014-11-09 02:02:37 +01:00
}
}
}
}
2016-07-27 23:43:46 +02:00
// 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
2016-07-27 23:43:46 +02:00
else if (dst) {
// destination track has something, but it isn't a chord
// remove rests from other voices if in "voice mode"
2016-07-27 23:43:46 +02:00
for (int i = 1; i < VOICES; ++i) {
Element* e = s->element(dstTrack + i);
if (e && e->isRest())
2016-07-27 23:43:46 +02:00
undoRemoveElement(e);
}
}
}
// delete orphaned spanners (TODO: figure out solution to reconnect orphaned spanners to their cloned notes)
checkSpanner(startTick, endTick);
2016-07-27 23:43:46 +02:00
}
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()));
}
2016-07-27 23:43:46 +02:00
// clone source tracks into destination
2016-07-27 23:43:46 +02:00
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));
2014-11-09 02:02:37 +01:00
}
}
}
// 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()) {
2017-03-08 13:12:26 +01:00
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
2017-01-18 14:16:33 +01:00
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;
2017-03-08 13:12:26 +01:00
for (Segment* ns = s->next(SegmentType::ChordRest); ns && ns != endSegment; ns = ns->next(SegmentType::ChordRest)) {
ChordRest* ncr = toChordRest(ns->element(track + voice));
2017-01-18 14:16:33 +01:00
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;
2016-12-13 13:16:17 +01:00
if (staff(staffIdx)->staffType(s->tick())->group() == StaffGroup::TAB)
line = staff(staffIdx)->lines(s->tick()) / 2;
else
2016-12-13 13:16:17 +01:00
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()) {
2017-12-20 16:49:30 +01:00
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
//---------------------------------------------------------
2013-05-13 18:49:17 +02:00
void Score::cmdSlashRhythm()
{
QList<Chord*> 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);
}
}
}
2019-05-21 07:08:09 +02:00
//---------------------------------------------------------
// cmdRealizeChordSymbols
/// Realize selected chord symbols into notes on the staff.
///
/// If a voicing and duration type are specified, the
/// harmony voicing settings will be overridden by the
/// passed parameters. Otherwise, the settings set on the
/// harmony object will be used.
//---------------------------------------------------------
void Score::cmdRealizeChordSymbols(bool literal, Voicing voicing, HDuration durationType)
{
const QList<Element*> elist = selection().elements();
for (Element* e : elist) {
if (!e->isHarmony())
continue;
Harmony* h = toHarmony(e);
if (!h->isRealizable())
continue;
RealizedHarmony r = h->getRealizedHarmony();
Segment* seg = toSegment(h->parent());
Fraction duration = r.getActualDuration(durationType);
Fraction tick = seg->tick();
bool concertPitch = styleB(Sid::concertPitch);
Chord* chord = new Chord(this); //chord template
chord->setTrack(h->track()); //set track so notes have a track to sit on
//create chord from notes
RealizedHarmony::PitchMap notes;
if (voicing == Voicing::INVALID || durationType == HDuration::INVALID)
notes = r.notes(); //no override, just use notes from realize harmony
else {
//generate notes list based on overridden settings
int offset = 0;
Interval interval = h->staff()->part()->instrument(h->tick())->transpose();
if (!concertPitch)
offset = interval.chromatic;
notes = r.generateNotes(h->rootTpc(), h->baseTpc(),
literal, voicing, offset);
}
RealizedHarmony::PitchMapIterator i(notes); //add notes to chord
while (i.hasNext()) {
i.next();
Note* note = new Note(this);
NoteVal nval;
nval.pitch = i.key();
if (concertPitch)
nval.tpc1 = i.value();
else
nval.tpc2 = i.value();
chord->add(note); //add note first to set track and such
note->setNval(nval, tick);
}
setChord(seg, h->track(), chord, duration); //add chord using template
delete chord;
}
}
//---------------------------------------------------------
// setChord
// return segment of last created chord
//---------------------------------------------------------
Segment* Score::setChord(Segment* segment, int track, Chord* chordTemplate, Fraction dur, Direction stemDirection)
{
Q_ASSERT(segment->segmentType() == SegmentType::ChordRest);
Fraction tick = segment->tick();
Chord* nr = 0; //current added chord used so we can select the last added chord and so we can apply ties
std::vector<Tie*> tie(chordTemplate->notes().size()); //keep pointer to a tie for each note in the chord in case we need to tie notes
ChordRest* cr = toChordRest(segment->element(track)); //chord rest under the segment for the specified track
bool addTie = false;
Measure* measure = 0;
//keep creating chords and tieing them until we created the full duration asked for (dur)
for (;;) {
if (track % VOICES)
expandVoice(segment, track);
Tuplet* t = cr ? cr->tuplet() : 0;
Fraction tDur = segment->ticks();
Segment* seg = segment->next();
//we need to get a correct subduration so that makeGap can function properly
//since makeGap() takes "normal" duration rather than actual length
while (seg) {
if (seg->segmentType() == SegmentType::ChordRest) {
//design choice made to keep multiple notes across a tuplet as tied single notes rather than combining them
//since it's arguably more readable, but the other code is still here (commented)
ChordRest* testCr = toChordRest(seg->element(track));
//code here allows us to combine tuplet realization together which I have opted not to do for readability (of the music)
//if (!!t ^ (testCr && testCr->tuplet())) //stop if we started with a tuplet and reach something that's not a tuplet,
// break; //or start with not a tuplet and reach a tuplet
if (testCr && testCr->tuplet()) //stop on tuplet
break;
tDur += seg->ticks();
}
if (tDur >= dur) { //do not go further than the duration asked for
tDur = dur;
break;
}
seg = seg->next(); //iterate only across measure (hence usage of next() rather than next1())
}
if (t)
tDur *= t->ratio(); //scale by tuplet ratio to get "normal" length rather than actual length when dealing with tuplets
// the returned gap ends at the measure boundary or at tuplet end
Fraction dd = makeGap(segment, track, tDur, t);
if (dd.isZero()) {
qDebug("cannot get gap at %d type: %d/%d", tick.ticks(), dur.numerator(),
dur.denominator());
break;
}
measure = segment->measure();
std::vector<TDuration> dl = toDurationList(dd, true);
size_t n = dl.size();
//add chord, tieing when necessary within measure
for (size_t i = 0; i < n; ++i) {
const TDuration& d = dl[i];
//create new chord from template and add it
Chord* chord = new Chord(*chordTemplate);
nr = chord;
chord->setTrack(track);
chord->setDurationType(d);
chord->setTicks(d.fraction());
chord->setStemDirection(stemDirection);
chord->setTuplet(t);
undoAddCR(chord, measure, tick);
//if there is something to tie, complete tie backwards
//and add the tie to score
const std::vector<Note*> notes = chord->notes();
if (addTie) {
for (size_t i = 0; i < notes.size(); ++i) {
tie[i]->setEndNote(notes[i]);
notes[i]->setTieBack(tie[i]);
undoAddElement(tie[i]);
}
addTie = false;
}
//if we're not the last element in the duration list,
//set tie forward
if (i+1 < n) {
for (size_t i = 0; i < notes.size(); ++i) {
tie[i] = new Tie(this);
tie[i]->setStartNote(notes[i]);
tie[i]->setTrack(track);
notes[i]->setTieFor(tie[i]);
addTie = true;
}
}
setPlayChord(true);
segment = chord->segment();
tick += chord->actualTicks();
}
//subtract the duration already realized and move on
if (t)
dur -= dd / t->ratio();
else
dur -= dd;
//we are done when there is no duration left to realize
if (dur.isZero())
break;
//go to next segment unless we are at the score (which means we will just be done there)
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
std::vector<Note*> notes = nr->notes();
for (size_t i = 0; i < notes.size(); ++i) {
tie[i] = new Tie(this);
tie[i]->setStartNote(notes[i]);
tie[i]->setTrack(notes[i]->track());
notes[i]->setTieFor(tie[i]);
}
}
if (!tie.empty())
connectTies();
if (nr)
select(nr, SelectType::SINGLE, 0);
return segment;
}
//---------------------------------------------------------
// 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()) {
2017-01-18 14:16:33 +01:00
if (e->type() == ElementType::REHEARSAL_MARK) {
RehearsalMark* rm = toRehearsalMark(e);
if (last) {
QString rmText = nextRehearsalMarkText(last, rm);
for (ScoreElement* le : rm->linkList())
2018-03-27 15:36:00 +02:00
le->undoChangeProperty(Pid::TEXT, rmText);
}
last = rm;
}
}
}
if (noSelection)
deselectAll();
}
//---------------------------------------------------------
// addRemoveBreaks
2016-03-24 12:39:18 +01:00
// 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);
}
2016-12-06 20:32:08 +01:00
//---------------------------------------------------------
// cmdPitchUp
//---------------------------------------------------------
void Score::cmdPitchUp()
{
Element* el = selection().element();
if (el && el->isLyrics())
cmdMoveLyrics(toLyrics(el), Direction::UP);
2018-04-18 14:31:36 +02:00
else if (el && (el->isArticulation() || el->isTextBase()))
el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF(0.0, -MScore::nudgeStep * el->spatium()), PropertyFlags::UNSTYLED);
2016-12-06 20:32:08 +01:00
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);
2018-04-18 14:31:36 +02:00
else if (el && (el->isArticulation() || el->isTextBase()))
el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF(0.0, MScore::nudgeStep * el->spatium()), PropertyFlags::UNSTYLED);
2016-12-06 20:32:08 +01:00
else if (el && el->isRest())
cmdMoveRest(toRest(el), Direction::DOWN);
else
upDown(false, UpDownMode::CHROMATIC);
}
//---------------------------------------------------------
// cmdTimeDelete
//---------------------------------------------------------
2016-12-09 15:56:48 +01:00
void Score::cmdTimeDelete()
2016-12-06 20:32:08 +01:00
{
Element* e = selection().element();
if (e && e->isBarLine() && toBarLine(e)->segment()->isEndBarLineType()) {
Measure* m = toBarLine(e)->segment()->measure();
cmdJoinMeasure(m, m->nextMeasure());
}
2016-12-09 15:56:48 +01:00
else {
if (_is.insertMode())
globalTimeDelete();
else
localTimeDelete();
}
2016-12-06 20:32:08 +01:00
}
//---------------------------------------------------------
// cmdPitchUpOctave
//---------------------------------------------------------
void Score::cmdPitchUpOctave()
{
Element* el = selection().element();
2018-04-18 14:31:36 +02:00
if (el && (el->isArticulation() || el->isTextBase()))
2018-12-22 02:58:02 +01:00
el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF(0.0, -MScore::nudgeStep10 * el->spatium()), PropertyFlags::UNSTYLED);
2016-12-06 20:32:08 +01:00
else
upDown(true, UpDownMode::OCTAVE);
}
//---------------------------------------------------------
// cmdPitchDownOctave
//---------------------------------------------------------
void Score::cmdPitchDownOctave()
{
Element* el = selection().element();
2018-04-18 14:31:36 +02:00
if (el && (el->isArticulation() || el->isTextBase()))
2018-12-22 02:58:02 +01:00
el->undoChangeProperty(Pid::OFFSET, el->offset() + QPointF(0.0, MScore::nudgeStep10 * el->spatium()), PropertyFlags::UNSTYLED);
2016-12-06 20:32:08 +01:00
else
upDown(false, UpDownMode::OCTAVE);
}
//---------------------------------------------------------
// cmdPadNoteInclreaseTAB
//---------------------------------------------------------
void Score::cmdPadNoteIncreaseTAB(const EditData& ed)
2016-12-06 20:32:08 +01:00
{
switch (_is.duration().type() ) {
// cycle back from longest to shortest?
// case TDuration::V_LONG:
// padToggle(Pad::NOTE128, ed);
2016-12-06 20:32:08 +01:00
// break;
case TDuration::DurationType::V_BREVE:
padToggle(Pad::NOTE00, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_WHOLE:
padToggle(Pad::NOTE0, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_HALF:
padToggle(Pad::NOTE1, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_QUARTER:
padToggle(Pad::NOTE2, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_EIGHTH:
padToggle(Pad::NOTE4, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_16TH:
padToggle(Pad::NOTE8, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_32ND:
padToggle(Pad::NOTE16, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_64TH:
padToggle(Pad::NOTE32, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_128TH:
padToggle(Pad::NOTE64, ed);
2016-12-06 20:32:08 +01:00
break;
default:
break;
}
}
//---------------------------------------------------------
// cmdPadNoteDecreaseTAB
//---------------------------------------------------------
void Score::cmdPadNoteDecreaseTAB(const EditData& ed)
2016-12-06 20:32:08 +01:00
{
switch (_is.duration().type() ) {
case TDuration::DurationType::V_LONG:
padToggle(Pad::NOTE0, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_BREVE:
padToggle(Pad::NOTE1, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_WHOLE:
padToggle(Pad::NOTE2, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_HALF:
padToggle(Pad::NOTE4, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_QUARTER:
padToggle(Pad::NOTE8, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_EIGHTH:
padToggle(Pad::NOTE16, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_16TH:
padToggle(Pad::NOTE32, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_32ND:
padToggle(Pad::NOTE64, ed);
2016-12-06 20:32:08 +01:00
break;
case TDuration::DurationType::V_64TH:
padToggle(Pad::NOTE128, ed);
2016-12-06 20:32:08 +01:00
break;
// cycle back from shortest to longest?
// case TDuration::DurationType::V_128TH:
// padToggle(Pad::NOTE00, ed);
2016-12-06 20:32:08 +01:00
// break;
default:
break;
}
}
//---------------------------------------------------------
// cmdToggleLayoutBreak
//---------------------------------------------------------
void Score::cmdToggleLayoutBreak(LayoutBreak::Type type)
{
// find measure(s)
QList<MeasureBase*> 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()) {
2019-04-06 07:54:38 +02:00
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;
}
}
}
2019-04-06 07:54:38 +02:00
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;
}
}
2016-12-06 20:32:08 +01:00
}
}
//---------------------------------------------------------
// cmdToggleMmrest
//---------------------------------------------------------
void Score::cmdToggleMmrest()
{
2018-03-27 15:36:00 +02:00
bool val = !styleB(Sid::createMultiMeasureRests);
2016-12-06 20:32:08 +01:00
deselectAll();
2018-03-27 15:36:00 +02:00
undo(new ChangeStyleVal(this, Sid::createMultiMeasureRests, val));
2016-12-06 20:32:08 +01:00
}
//---------------------------------------------------------
// cmdToggleHideEmpty
//---------------------------------------------------------
void Score::cmdToggleHideEmpty()
{
2018-03-27 15:36:00 +02:00
bool val = !styleB(Sid::hideEmptyStaves);
2016-12-06 20:32:08 +01:00
deselectAll();
2018-03-27 15:36:00 +02:00
undo(new ChangeStyleVal(this, Sid::hideEmptyStaves, val));
2016-12-06 20:32:08 +01:00
}
//---------------------------------------------------------
// cmdSetVisible
//---------------------------------------------------------
void Score::cmdSetVisible()
{
for (Element* e : selection().elements())
2018-03-27 15:36:00 +02:00
undo(new ChangeProperty(e, Pid::VISIBLE, true));
2016-12-06 20:32:08 +01:00
}
//---------------------------------------------------------
// cmdUnsetVisible
//---------------------------------------------------------
void Score::cmdUnsetVisible()
{
for (Element* e : selection().elements())
2018-03-27 15:36:00 +02:00
undo(new ChangeProperty(e, Pid::VISIBLE, false));
2016-12-06 20:32:08 +01:00
}
2017-05-03 16:20:04 +02:00
//---------------------------------------------------------
// 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);
2017-05-03 16:20:04 +02:00
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);
2017-05-03 16:20:04 +02:00
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()) {
2017-05-03 16:20:04 +02:00
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()) {
2017-05-03 16:20:04 +02:00
curPitch = line2pitch(4, clef->clefType(), Key::C); // C 72 for treble clef
break;
}
}
}
seg = seg->prev1MM(SegmentType::ChordRest | SegmentType::Clef | SegmentType::HeaderClef);
2017-05-03 16:20:04 +02:00
}
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;
2017-05-03 17:31:50 +02:00
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);
2017-05-03 16:20:04 +02:00
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);
2017-05-03 16:20:04 +02:00
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);
2017-05-03 16:20:04 +02:00
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);
2017-05-03 16:20:04 +02:00
}
//---------------------------------------------------------
// cmdToggleVisible
//---------------------------------------------------------
void Score::cmdToggleVisible()
{
QSet<Element*> 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;
2017-05-03 16:20:04 +02:00
bool spannerSegment = e->isSpannerSegment();
2017-12-20 16:49:30 +01:00
if (!spannerSegment || !spanners.contains(toSpannerSegment(e)->spanner()))
2018-03-27 15:36:00 +02:00
e->undoChangeProperty(Pid::VISIBLE, !e->getProperty(Pid::VISIBLE).toBool());
2017-05-03 16:20:04 +02:00
if (spannerSegment)
2017-12-20 16:49:30 +01:00
spanners.insert(toSpannerSegment(e)->spanner());
2017-05-03 16:20:04 +02:00
}
}
//---------------------------------------------------------
// 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 = staff(pos.staffIdx)->staffType(is.tick())->physStringToVisual(is.string());
2017-05-03 16:20:04 +02:00
pos.fret = fret;
putNote(pos, false);
}
2017-07-17 16:11:21 +02:00
//---------------------------------------------------------
// 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<Element*> 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);
}
}
}
2016-12-06 20:32:08 +01:00
//---------------------------------------------------------
// cmd
//---------------------------------------------------------
2017-05-03 16:20:04 +02:00
void Score::cmd(const QAction* a, EditData& ed)
2016-12-06 20:32:08 +01:00
{
QString cmd(a ? a->data().toString() : "");
if (MScore::debugMode)
2016-12-23 12:05:18 +01:00
qDebug("<%s>", qPrintable(cmd));
2016-12-06 20:32:08 +01:00
struct ScoreCmd {
const char* name;
std::function<void(Score* cs, EditData& ed)> cmd;
2016-12-06 20:32:08 +01:00
};
static const std::vector<ScoreCmd> cmdList {
{ "note-c", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 0, false, false); }},
{ "note-d", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 1, false, false); }},
{ "note-e", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 2, false, false); }},
{ "note-f", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 3, false, false); }},
{ "note-g", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 4, false, false); }},
{ "note-a", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 5, false, false); }},
{ "note-b", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 6, false, false); }},
{ "chord-c", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 0, true, false); }},
{ "chord-d", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 1, true, false); }},
{ "chord-e", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 2, true, false); }},
{ "chord-f", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 3, true, false); }},
{ "chord-g", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 4, true, false); }},
{ "chord-a", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 5, true, false); }},
{ "chord-b", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 6, true, false); }},
{ "insert-c", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 0, false, true); }},
{ "insert-d", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 1, false, true); }},
{ "insert-e", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 2, false, true); }},
{ "insert-f", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 3, false, true); }},
{ "insert-g", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 4, false, true); }},
{ "insert-a", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 5, false, true); }},
{ "insert-b", [](Score* cs, EditData& ed){ cs->cmdAddPitch(ed, 6, false, true); }},
{ "fret-0", [](Score* cs, EditData&){ cs->cmdAddFret(0); }},
{ "fret-1", [](Score* cs, EditData&){ cs->cmdAddFret(1); }},
{ "fret-2", [](Score* cs, EditData&){ cs->cmdAddFret(2); }},
{ "fret-3", [](Score* cs, EditData&){ cs->cmdAddFret(3); }},
{ "fret-4", [](Score* cs, EditData&){ cs->cmdAddFret(4); }},
{ "fret-5", [](Score* cs, EditData&){ cs->cmdAddFret(5); }},
{ "fret-6", [](Score* cs, EditData&){ cs->cmdAddFret(6); }},
{ "fret-7", [](Score* cs, EditData&){ cs->cmdAddFret(7); }},
{ "fret-8", [](Score* cs, EditData&){ cs->cmdAddFret(8); }},
{ "fret-9", [](Score* cs, EditData&){ cs->cmdAddFret(9); }},
{ "fret-10", [](Score* cs, EditData&){ cs->cmdAddFret(10); }},
{ "fret-11", [](Score* cs, EditData&){ cs->cmdAddFret(11); }},
{ "fret-12", [](Score* cs, EditData&){ cs->cmdAddFret(12); }},
{ "fret-13", [](Score* cs, EditData&){ cs->cmdAddFret(13); }},
{ "fret-14", [](Score* cs, EditData&){ cs->cmdAddFret(14); }},
{ "toggle-visible", [](Score* cs, EditData&){ cs->cmdToggleVisible(); }},
{ "reset-stretch", [](Score* cs, EditData&){ cs->resetUserStretch(); }},
{ "mirror-note", [](Score* cs, EditData&){ cs->cmdMirrorNoteHead(); }},
{ "double-duration", [](Score* cs, EditData&){ cs->cmdDoubleDuration(); }},
{ "half-duration", [](Score* cs, EditData&){ cs->cmdHalfDuration(); }},
{ "inc-duration-dotted", [](Score* cs, EditData&){ cs->cmdIncDurationDotted(); }},
{ "dec-duration-dotted", [](Score* cs, EditData&){ cs->cmdDecDurationDotted(); }},
{ "add-staccato", [](Score* cs, EditData&){ cs->addArticulation(SymId::articStaccatoAbove); }},
{ "add-tenuto", [](Score* cs, EditData&){ cs->addArticulation(SymId::articTenutoAbove); }},
{ "add-marcato", [](Score* cs, EditData&){ cs->addArticulation(SymId::articMarcatoAbove); }},
{ "add-sforzato", [](Score* cs, EditData&){ cs->addArticulation(SymId::articAccentAbove); }},
{ "add-trill", [](Score* cs, EditData&){ cs->addArticulation(SymId::ornamentTrill); }},
{ "add-up-bow", [](Score* cs, EditData&){ cs->addArticulation(SymId::stringsUpBow); }},
{ "add-down-bow", [](Score* cs, EditData&){ cs->addArticulation(SymId::stringsDownBow); }},
{ "add-8va", [](Score* cs, EditData&){ cs->cmdAddOttava(OttavaType::OTTAVA_8VA); }},
{ "add-8vb", [](Score* cs, EditData&){ cs->cmdAddOttava(OttavaType::OTTAVA_8VB); }},
{ "note-longa", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE00, ed); }},
{ "note-longa-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE00, ed); }},
{ "note-breve", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE0, ed); }},
{ "note-breve-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE0, ed); }},
{ "pad-note-1", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE1, ed); }},
{ "pad-note-1-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE1, ed); }},
{ "pad-note-2", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE2, ed); }},
{ "pad-note-2-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE2, ed); }},
{ "pad-note-4", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE4, ed); }},
{ "pad-note-4-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE4, ed); }},
{ "pad-note-8", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE8, ed); }},
{ "pad-note-8-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE8, ed); }},
{ "pad-note-16", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE16, ed); }},
{ "pad-note-16-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE16, ed); }},
{ "pad-note-32", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE32, ed); }},
{ "pad-note-32-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE32, ed); }},
{ "pad-note-64", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE64, ed); }},
{ "pad-note-64-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE64, ed); }},
{ "pad-note-128", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE128, ed); }},
{ "pad-note-128-TAB", [](Score* cs, EditData& ed){ cs->padToggle(Pad::NOTE128, ed); }},
{ "reset-style", [](Score* cs, EditData&){ cs->cmdResetStyle(); }},
{ "reset-beammode", [](Score* cs, EditData&){ cs->cmdResetBeamMode(); }},
{ "reset-groupings", [](Score* cs, EditData&){ cs->cmdResetNoteAndRestGroupings(); }},
{ "clef-violin", [](Score* cs, EditData&){ cs->cmdInsertClef(ClefType::G); }},
{ "clef-bass", [](Score* cs, EditData&){ cs->cmdInsertClef(ClefType::F); }},
{ "voice-x12", [](Score* cs, EditData&){ cs->cmdExchangeVoice(0, 1); }},
{ "voice-x13", [](Score* cs, EditData&){ cs->cmdExchangeVoice(0, 2); }},
{ "voice-x14", [](Score* cs, EditData&){ cs->cmdExchangeVoice(0, 3); }},
{ "voice-x23", [](Score* cs, EditData&){ cs->cmdExchangeVoice(1, 2); }},
{ "voice-x24", [](Score* cs, EditData&){ cs->cmdExchangeVoice(1, 3); }},
{ "voice-x34", [](Score* cs, EditData&){ cs->cmdExchangeVoice(2, 3); }},
{ "pad-rest", [](Score* cs, EditData& ed){ cs->padToggle(Pad::REST, ed); }},
{ "pad-dot", [](Score* cs, EditData& ed){ cs->padToggle(Pad::DOT, ed); }},
{ "pad-dotdot", [](Score* cs, EditData& ed){ cs->padToggle(Pad::DOTDOT, ed); }},
{ "pad-dot3", [](Score* cs, EditData& ed){ cs->padToggle(Pad::DOT3, ed); }},
{ "pad-dot4", [](Score* cs, EditData& ed){ cs->padToggle(Pad::DOT4, ed); }},
{ "beam-start", [](Score* cs, EditData&){ cs->cmdSetBeamMode(Beam::Mode::BEGIN); }},
{ "beam-mid", [](Score* cs, EditData&){ cs->cmdSetBeamMode(Beam::Mode::MID); }},
{ "no-beam", [](Score* cs, EditData&){ cs->cmdSetBeamMode(Beam::Mode::NONE); }},
{ "beam32", [](Score* cs, EditData&){ cs->cmdSetBeamMode(Beam::Mode::BEGIN32); }},
{ "beam64", [](Score* cs, EditData&){ cs->cmdSetBeamMode(Beam::Mode::BEGIN64); }},
{ "auto-beam", [](Score* cs, EditData&){ cs->cmdSetBeamMode(Beam::Mode::AUTO); }},
{ "sharp2", [](Score* cs, EditData& ed){ cs->toggleAccidental(AccidentalType::SHARP2, ed); }},
{ "sharp", [](Score* cs, EditData& ed){ cs->toggleAccidental(AccidentalType::SHARP, ed); }},
{ "nat", [](Score* cs, EditData& ed){ cs->toggleAccidental(AccidentalType::NATURAL, ed); }},
{ "flat", [](Score* cs, EditData& ed){ cs->toggleAccidental(AccidentalType::FLAT, ed); }},
{ "flat2", [](Score* cs, EditData& ed){ cs->toggleAccidental(AccidentalType::FLAT2, ed); }},
{ "flip", [](Score* cs, EditData&){ cs->cmdFlip(); }},
{ "stretch+", [](Score* cs, EditData&){ cs->cmdAddStretch(0.1); }},
{ "stretch-", [](Score* cs, EditData&){ cs->cmdAddStretch(-0.1); }},
{ "pitch-spell", [](Score* cs, EditData&){ cs->spell(); }},
{ "select-all", [](Score* cs, EditData&){ cs->cmdSelectAll(); }},
{ "select-section", [](Score* cs, EditData&){ cs->cmdSelectSection(); }},
{ "add-brackets", [](Score* cs, EditData&){ cs->cmdAddBracket(); }},
{ "add-parentheses", [](Score* cs, EditData&){ cs->cmdAddParentheses(); }},
{ "acciaccatura", [](Score* cs, EditData&){ cs->cmdAddGrace(NoteType::ACCIACCATURA, MScore::division / 2); }},
{ "appoggiatura", [](Score* cs, EditData&){ cs->cmdAddGrace(NoteType::APPOGGIATURA, MScore::division / 2); }},
{ "grace4", [](Score* cs, EditData&){ cs->cmdAddGrace(NoteType::GRACE4, MScore::division); }},
{ "grace16", [](Score* cs, EditData&){ cs->cmdAddGrace(NoteType::GRACE16, MScore::division / 4); }},
{ "grace32", [](Score* cs, EditData&){ cs->cmdAddGrace(NoteType::GRACE32, MScore::division / 8); }},
{ "grace8after", [](Score* cs, EditData&){ cs->cmdAddGrace(NoteType::GRACE8_AFTER, MScore::division / 2); }},
{ "grace16after", [](Score* cs, EditData&){ cs->cmdAddGrace(NoteType::GRACE16_AFTER, MScore::division / 4); }},
{ "grace32after", [](Score* cs, EditData&){ cs->cmdAddGrace(NoteType::GRACE32_AFTER, MScore::division / 8); }},
{ "explode", [](Score* cs, EditData&){ cs->cmdExplode(); }},
2019-05-21 07:08:09 +02:00
{ "implode", [](Score* cs, EditData&){ cs->cmdImplode(); }},
{ "slash-fill", [](Score* cs, EditData&){ cs->cmdSlashFill(); }},
{ "slash-rhythm", [](Score* cs, EditData&){ cs->cmdSlashRhythm(); }},
{ "resequence-rehearsal-marks", [](Score* cs, EditData&){ cs->cmdResequenceRehearsalMarks(); }},
{ "del-empty-measures", [](Score* cs, EditData&){ cs->cmdRemoveEmptyTrailingMeasures(); }},
{ "add-audio", [](Score* cs, EditData&){ cs->addAudioTrack(); }},
{ "transpose-up", [](Score* cs, EditData&){ cs->transposeSemitone(1); }},
{ "transpose-down", [](Score* cs, EditData&){ cs->transposeSemitone(-1); }},
{ "delete", [](Score* cs, EditData&){ cs->cmdDeleteSelection(); }},
{ "full-measure-rest", [](Score* cs, EditData&){ cs->cmdFullMeasureRest(); }},
{ "toggle-insert-mode", [](Score* cs, EditData&){ cs->_is.setInsertMode(!cs->_is.insertMode()); }},
{ "pitch-up", [](Score* cs, EditData&){ cs->cmdPitchUp(); }},
{ "pitch-down", [](Score* cs, EditData&){ cs->cmdPitchDown(); }},
{ "time-delete", [](Score* cs, EditData&){ cs->cmdTimeDelete(); }},
{ "pitch-up-octave", [](Score* cs, EditData&){ cs->cmdPitchUpOctave(); }},
{ "pitch-down-octave", [](Score* cs, EditData&){ cs->cmdPitchDownOctave(); }},
{ "pad-note-increase", [](Score* cs, EditData& ed){ cs->cmdPadNoteIncreaseTAB(ed); }},
{ "pad-note-decrease", [](Score* cs, EditData& ed){ cs->cmdPadNoteDecreaseTAB(ed); }},
{ "pad-note-increase-TAB", [](Score* cs, EditData& ed){ cs->cmdPadNoteIncreaseTAB(ed); }},
{ "pad-note-decrease-TAB", [](Score* cs, EditData& ed){ cs->cmdPadNoteDecreaseTAB(ed); }},
{ "toggle-mmrest", [](Score* cs, EditData&){ cs->cmdToggleMmrest(); }},
{ "toggle-hide-empty", [](Score* cs, EditData&){ cs->cmdToggleHideEmpty(); }},
{ "set-visible", [](Score* cs, EditData&){ cs->cmdSetVisible(); }},
{ "unset-visible", [](Score* cs, EditData&){ cs->cmdUnsetVisible(); }},
{ "system-break", [](Score* cs, EditData&){ cs->cmdToggleLayoutBreak(LayoutBreak::Type::LINE); }},
{ "page-break", [](Score* cs, EditData&){ cs->cmdToggleLayoutBreak(LayoutBreak::Type::PAGE); }},
{ "section-break", [](Score* cs, EditData&){ cs->cmdToggleLayoutBreak(LayoutBreak::Type::SECTION); }},
{ "relayout", [](Score* cs, EditData&){ cs->cmdRelayout(); }},
{ "toggle-autoplace", [](Score* cs, EditData&){ cs->cmdToggleAutoplace(false); }},
{ "autoplace-enabled", [](Score* cs, EditData&){ cs->cmdToggleAutoplace(true); }},
2016-12-06 20:32:08 +01:00
};
for (const auto& c : cmdList) {
if (cmd == c.name) {
startCmd();
c.cmd(this, ed);
2016-12-06 20:32:08 +01:00
endCmd();
return;
}
}
qDebug("unknown cmd <%s>", qPrintable(cmd));
}
}