From 9346ef12ad0b4cc234cf23963771248e987b2db1 Mon Sep 17 00:00:00 2001 From: Dmitri Ovodok Date: Thu, 14 May 2020 06:21:22 +0300 Subject: [PATCH] Plugin API: add tuplets support --- libmscore/duration.h | 6 --- mscore/plugin/api/cursor.cpp | 81 +++++++++++++++++++++++++++++++ mscore/plugin/api/cursor.h | 3 ++ mscore/plugin/api/elements.cpp | 32 +++++++++++++ mscore/plugin/api/elements.h | 88 ++++++++++++++++++++++++++++++---- 5 files changed, 195 insertions(+), 15 deletions(-) diff --git a/libmscore/duration.h b/libmscore/duration.h index 862acf34df9bf..78ddccd864433 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/mscore/plugin/api/cursor.cpp b/mscore/plugin/api/cursor.cpp index 87e8cdf9271b7..29f3844e64ad1 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 { @@ -407,6 +408,86 @@ void Cursor::addRest() _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; + } + + 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::Measure* tupletMeasure = segment()->measure(); + const Ms::Fraction tupletTick = segment()->tick(); + + 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 ba8c660442ab3..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; @@ -183,6 +185,7 @@ class Cursor : public QObject { 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 b0b9f90b99bbf..018059fe01593 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); @@ -181,12 +184,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 ) @@ -284,7 +281,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 ) @@ -520,12 +516,86 @@ 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 ) + + 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); } + /// \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 ) @@ -544,7 +614,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); }