diff --git a/libmscore/duration.h b/libmscore/duration.h index 9b920ca8ffe0f..9a251f39ab7f6 100644 --- a/libmscore/duration.h +++ b/libmscore/duration.h @@ -35,12 +35,6 @@ class DurationElement : public Element { Fraction _duration; Tuplet* _tuplet; -// #ifdef SCRIPT_INTERFACE -// void setDurationW(FractionWrapper* f) { _duration = f->fraction(); } -// FractionWrapper* durationW() const { return new FractionWrapper(_duration); } -// FractionWrapper* globalDurW() const { return new FractionWrapper(globalDuration()); } -// #endif - public: DurationElement(Score* = 0, ElementFlags = ElementFlag::MOVABLE | ElementFlag::ON_STAFF); DurationElement(const DurationElement& e); diff --git a/libmscore/edit.cpp b/libmscore/edit.cpp index 9c1c1e3d4abfe..cc6e7293b81c1 100644 --- a/libmscore/edit.cpp +++ b/libmscore/edit.cpp @@ -2848,19 +2848,31 @@ void Score::cmdEnterRest(const TDuration& d) return; } startCmd(); - expandVoice(); - if (_is.cr() == 0) { + enterRest(d); + endCmd(); + } + +//--------------------------------------------------------- +// enterRest +//--------------------------------------------------------- + +void Score::enterRest(const TDuration& d, InputState* externalInputState) + { + InputState& is = externalInputState ? (*externalInputState) : _is; + + expandVoice(is.segment(), is.track()); + + if (!is.cr()) { qDebug("cannot enter rest here"); return; } - int track = _is.track(); + const int track = is.track(); NoteVal nval; - setNoteRest(_is.segment(), track, nval, d.fraction(), Direction::AUTO); - _is.moveToNextInputPos(); - if (!noteEntryMode() || usingNoteEntryMethod(NoteEntryMethod::STEPTIME)) - _is.setRest(false); // continue with normal note entry - endCmd(); + setNoteRest(is.segment(), track, nval, d.fraction(), Direction::AUTO, /* forceAccidental */ false, /* rhythmic */ false, externalInputState); + is.moveToNextInputPos(); + if (!is.noteEntryMode() || is.usingNoteEntryMethod(NoteEntryMethod::STEPTIME)) + is.setRest(false); // continue with normal note entry } //--------------------------------------------------------- diff --git a/libmscore/score.h b/libmscore/score.h index 783a3185ae500..072251f38c84d 100644 --- a/libmscore/score.h +++ b/libmscore/score.h @@ -1033,6 +1033,7 @@ class Score : public QObject, public ScoreElement { Element* selectMove(const QString& cmd); Element* move(const QString& cmd); void cmdEnterRest(const TDuration& d); + void enterRest(const TDuration& d, InputState* externalInputState = nullptr); void cmdAddInterval(int, const std::vector&); void cmdCreateTuplet(ChordRest*, Tuplet*); void removeAudio(); diff --git a/mscore/plugin/api/cursor.cpp b/mscore/plugin/api/cursor.cpp index b56a5fd6d6c3d..2ed7e784870fa 100644 --- a/mscore/plugin/api/cursor.cpp +++ b/mscore/plugin/api/cursor.cpp @@ -25,6 +25,7 @@ #include "libmscore/system.h" #include "libmscore/segment.h" #include "libmscore/timesig.h" +#include "libmscore/tuplet.h" namespace Ms { namespace PluginAPI { @@ -388,6 +389,112 @@ void Cursor::addNote(int pitch, bool addToChord) _score->addPitch(nval, addToChord, is.get()); } +//--------------------------------------------------------- +// addRest +/// \brief Adds a rest to the current cursor position. +/// \details The duration of the added rest equals to +/// what has been set by the previous setDuration() call. +/// \since MuseScore 3.5 +//--------------------------------------------------------- + +void Cursor::addRest() + { + if (!segment()) { + qWarning("Cursor::addRest: cursor location is undefined, use rewind() to define its location"); + return; + } + if (!inputState().duration().isValid()) + setDuration(1, 4); + _score->enterRest(inputState().duration(), is.get()); + } + +//--------------------------------------------------------- +// addTuplet +/// \brief Adds a tuplet to the current cursor position. +/// \details This function provides a possibility to setup +/// the tuplet's ratio to any value (similarly to +/// Add > Tuplets > Other... dialog in MuseScore). +/// +/// Examples of most typical usage: +/// \code +/// // add a triplet of three eighth notes +/// cursor.addTuplet(fraction(3, 2), fraction(1, 4)); +/// +/// // add a quintuplet in place of the current chord/rest +/// var cr = cursor.element; +/// if (cr) +/// cursor.addTuplet(fraction(5, 4), cr.duration); +/// \endcode +/// +/// \param ratio tuplet ratio. Numerator represents +/// actual number of notes in this tuplet, denominator is +/// a number of "normal" notes which correspond to the +/// same total duration. For example, a triplet has a +/// ratio of 3/2 as it has 3 notes fitting to the +/// duration which would normally be occupied by 2 notes +/// of the same nominal length. +/// \param duration total duration of the tuplet. To +/// create a tuplet with duration matching to duration of +/// existing chord or rest, use its +/// \ref DurationElement.duration "duration" value as +/// a parameter. +/// \since MuseScore 3.5 +/// \see \ref DurationElement.tuplet +//--------------------------------------------------------- + +void Cursor::addTuplet(FractionWrapper* ratio, FractionWrapper* duration) + { + if (!segment()) { + qWarning("Cursor::addTuplet: cursor location is undefined, use rewind() to define its location"); + return; + } + + const Ms::Fraction fRatio = ratio->fraction(); + const Ms::Fraction fDuration = duration->fraction(); + + if (!fRatio.isValid() || fRatio.isZero() || fRatio.negative() + || !fDuration.isValid() || fDuration.isZero() || fDuration.negative()) { + qWarning("Cursor::addTuplet: invalid parameter values: %s, %s", qPrintable(fRatio.toString()), qPrintable(fDuration.toString())); + return; + } + + Ms::Measure* tupletMeasure = segment()->measure(); + const Ms::Fraction tupletTick = segment()->tick(); + + if (tupletTick + fDuration > tupletMeasure->endTick()) { + qWarning( + "Cursor::addTuplet: cannot add cross-measure tuplet (measure %d, rel.tick %s, duration %s)", + tupletMeasure->no() + 1, qPrintable(segment()->rtick().toString()), qPrintable(fDuration.toString())); + return; + } + + const Ms::Fraction baseLen = fDuration * Fraction(1, fRatio.denominator()); + if (!TDuration::isValid(baseLen)) { + qWarning("Cursor::addTuplet: cannot create tuplet for ratio %s and duration %s", qPrintable(fRatio.toString()), qPrintable(fDuration.toString())); + return; + } + + _score->expandVoice(inputState().segment(), inputState().track()); + Ms::ChordRest* cr = inputState().cr(); + if (!cr) // shouldn't happen? + return; + + _score->changeCRlen(cr, fDuration); + + Ms::Tuplet* tuplet = new Ms::Tuplet(_score); + tuplet->setParent(tupletMeasure); + tuplet->setTrack(track()); + tuplet->setTick(tupletTick); + tuplet->setRatio(fRatio); + tuplet->setTicks(fDuration); + tuplet->setBaseLen(baseLen); + + _score->cmdCreateTuplet(cr, tuplet); + + inputState().setSegment(tupletMeasure->tick2segment(tupletTick)); + inputState().setDuration(baseLen); + } + //--------------------------------------------------------- // setDuration /// Set duration of the notes added by the cursor. diff --git a/mscore/plugin/api/cursor.h b/mscore/plugin/api/cursor.h index 877dc9ffb306a..50aef45be9a09 100644 --- a/mscore/plugin/api/cursor.h +++ b/mscore/plugin/api/cursor.h @@ -13,6 +13,8 @@ #ifndef __CURSOR_H__ #define __CURSOR_H__ +#include "fraction.h" + namespace Ms { class Element; @@ -182,6 +184,8 @@ class Cursor : public QObject { Q_INVOKABLE void add(Ms::PluginAPI::Element*); Q_INVOKABLE void addNote(int pitch, bool addToChord = false); + Q_INVOKABLE void addRest(); + Q_INVOKABLE void addTuplet(Ms::PluginAPI::FractionWrapper* ratio, Ms::PluginAPI::FractionWrapper* duration); //@ set duration //@ z: numerator diff --git a/mscore/plugin/api/elements.cpp b/mscore/plugin/api/elements.cpp index 3e9066cd2c786..ad18e26ea05f7 100644 --- a/mscore/plugin/api/elements.cpp +++ b/mscore/plugin/api/elements.cpp @@ -11,6 +11,7 @@ //============================================================================= #include "elements.h" +#include "fraction.h" #include "libmscore/property.h" #include "libmscore/undo.h" @@ -166,6 +167,33 @@ void Note::remove(Ms::PluginAPI::Element* wrapped) qDebug("Note::remove() not impl. %s", s->name()); } +//--------------------------------------------------------- +// DurationElement::globalDuration +//--------------------------------------------------------- + +FractionWrapper* DurationElement::globalDuration() const + { + return wrap(durationElement()->globalTicks()); + } + +//--------------------------------------------------------- +// DurationElement::actualDuration +//--------------------------------------------------------- + +FractionWrapper* DurationElement::actualDuration() const + { + return wrap(durationElement()->actualTicks()); + } + +//--------------------------------------------------------- +// DurationElement::parentTuplet +//--------------------------------------------------------- + +Tuplet* DurationElement::parentTuplet() + { + return wrap(durationElement()->tuplet()); + } + //--------------------------------------------------------- // Chord::setPlayEventType //--------------------------------------------------------- @@ -264,6 +292,8 @@ Element* wrap(Ms::Element* e, Ownership own) return wrap(toNote(e), own); case ElementType::CHORD: return wrap(toChord(e), own); + case ElementType::TUPLET: + return wrap(toTuplet(e), own); case ElementType::SEGMENT: return wrap(toSegment(e), own); case ElementType::MEASURE: @@ -271,6 +301,8 @@ Element* wrap(Ms::Element* e, Ownership own) case ElementType::PAGE: return wrap(toPage(e), own); default: + if (e->isDurationElement()) + return wrap(toDurationElement(e), own); break; } return wrap(e, own); diff --git a/mscore/plugin/api/elements.h b/mscore/plugin/api/elements.h index fdd68083fdd8c..bf4617d08298c 100644 --- a/mscore/plugin/api/elements.h +++ b/mscore/plugin/api/elements.h @@ -22,6 +22,7 @@ #include "libmscore/notedot.h" #include "libmscore/page.h" #include "libmscore/segment.h" +#include "libmscore/tuplet.h" #include "libmscore/accidental.h" #include "libmscore/musescoreCore.h" #include "libmscore/score.h" @@ -32,7 +33,9 @@ namespace Ms { namespace PluginAPI { +class FractionWrapper; class Element; +class Tuplet; class Tie; extern Tie* tieWrap(Ms::Tie* tie); @@ -184,12 +187,6 @@ class Element : public Ms::PluginAPI::ScoreElement { API_PROPERTY( play, PLAY ) API_PROPERTY( timesigNominal, TIMESIG_NOMINAL ) API_PROPERTY( timesigActual, TIMESIG_ACTUAL ) - API_PROPERTY( numberType, NUMBER_TYPE ) - API_PROPERTY( bracketType, BRACKET_TYPE ) - API_PROPERTY( normalNotes, NORMAL_NOTES ) - API_PROPERTY( actualNotes, ACTUAL_NOTES ) - API_PROPERTY( p1, P1 ) - API_PROPERTY( p2, P2 ) API_PROPERTY( growLeft, GROW_LEFT ) API_PROPERTY( growRight, GROW_RIGHT ) API_PROPERTY( boxHeight, BOX_HEIGHT ) @@ -287,7 +284,6 @@ class Element : public Ms::PluginAPI::ScoreElement { API_PROPERTY( lineVisible, LINE_VISIBLE ) API_PROPERTY( mag, MAG ) API_PROPERTY( useDrumset, USE_DRUMSET ) - API_PROPERTY( duration, DURATION ) API_PROPERTY( durationType, DURATION_TYPE ) API_PROPERTY( role, ROLE ) API_PROPERTY_T( int, track, TRACK ) @@ -523,12 +519,94 @@ class Note : public Element { Q_INVOKABLE void remove(Ms::PluginAPI::Element* wrapped); }; +//--------------------------------------------------------- +// DurationElement +//--------------------------------------------------------- + +class DurationElement : public Element { + Q_OBJECT + + /** + * Nominal duration of this element. + * The duration is represented as a fraction of whole note length. + */ + API_PROPERTY_READ_ONLY( duration, DURATION ) + /** + * Global duration of this element, taking into account ratio of + * parent tuplets if there are any. + * \since MuseScore 3.5 + */ + Q_PROPERTY(Ms::PluginAPI::FractionWrapper* globalDuration READ globalDuration) + /** + * Actual duration of this element, taking into account ratio of + * parent tuplets and local time signatures if there are any. + * \since MuseScore 3.5 + */ + Q_PROPERTY(Ms::PluginAPI::FractionWrapper* actualDuration READ actualDuration) + /** + * Tuplet which this element belongs to. If there is no parent tuplet, returns null. + * \since MuseScore 3.5 + */ + Q_PROPERTY(Ms::PluginAPI::Tuplet* tuplet READ parentTuplet) + + public: + /// \cond MS_INTERNAL + DurationElement(Ms::DurationElement* de = nullptr, Ownership own = Ownership::PLUGIN) + : Element(de, own) {} + + Ms::DurationElement* durationElement() { return toDurationElement(e); } + const Ms::DurationElement* durationElement() const { return toDurationElement(e); } + + FractionWrapper* globalDuration() const; + FractionWrapper* actualDuration() const; + + Tuplet* parentTuplet(); + /// \endcond + }; + +//--------------------------------------------------------- +// Tuplet +//--------------------------------------------------------- + +class Tuplet : public DurationElement { + Q_OBJECT + + API_PROPERTY( numberType, NUMBER_TYPE ) + API_PROPERTY( bracketType, BRACKET_TYPE ) + /** Actual number of notes of base nominal length in this tuplet. */ + API_PROPERTY_READ_ONLY_T( int, actualNotes, ACTUAL_NOTES ) + /** + * Number of "normal" notes of base nominal length which correspond + * to this tuplet's duration. + */ + API_PROPERTY_READ_ONLY_T( int, normalNotes, NORMAL_NOTES ) + API_PROPERTY( p1, P1 ) + API_PROPERTY( p2, P2 ) + + /** + * List of elements which belong to this tuplet. + * \since MuseScore 3.5 + */ + Q_PROPERTY(QQmlListProperty elements READ elements) + + public: + /// \cond MS_INTERNAL + Tuplet(Ms::Tuplet* t = nullptr, Ownership own = Ownership::PLUGIN) + : DurationElement(t, own) {} + + Ms::Tuplet* tuplet() { return toTuplet(e); } + const Ms::Tuplet* tuplet() const { return toTuplet(e); } + + QQmlListProperty elements() { return wrapContainerProperty(this, tuplet()->elements()); } + /// \endcond + }; + //--------------------------------------------------------- // Chord // Chord wrapper //--------------------------------------------------------- -class Chord : public Element { +class Chord : public DurationElement { Q_OBJECT Q_PROPERTY(QQmlListProperty graceNotes READ graceNotes) Q_PROPERTY(QQmlListProperty notes READ notes ) @@ -547,7 +625,7 @@ class Chord : public Element { public: /// \cond MS_INTERNAL Chord(Ms::Chord* c = nullptr, Ownership own = Ownership::PLUGIN) - : Element(c, own) {} + : DurationElement(c, own) {} Ms::Chord* chord() { return toChord(e); } const Ms::Chord* chord() const { return toChord(e); }