Plugin API: add basic API for access to Instruments and Channels

This commit is contained in:
Dmitri Ovodok 2020-05-15 10:06:38 +03:00 committed by Igor Korsukov
parent 2216872a7f
commit a11883241d
7 changed files with 562 additions and 0 deletions

View file

@ -49,6 +49,9 @@ set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/api/excerpt.cpp
${CMAKE_CURRENT_LIST_DIR}/api/excerpt.h
${CMAKE_CURRENT_LIST_DIR}/api/fraction.h
${CMAKE_CURRENT_LIST_DIR}/api/instrument.cpp
${CMAKE_CURRENT_LIST_DIR}/api/instrument.h
${CMAKE_CURRENT_LIST_DIR}/api/part.cpp
${CMAKE_CURRENT_LIST_DIR}/api/part.h
${CMAKE_CURRENT_LIST_DIR}/api/playevent.cpp
${CMAKE_CURRENT_LIST_DIR}/api/playevent.h

View file

@ -0,0 +1,164 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "instrument.h"
#include "framework/midi_old/midipatch.h"
#include "libmscore/part.h"
#include "libmscore/score.h"
#include "libmscore/undo.h"
namespace Ms {
namespace PluginAPI {
//---------------------------------------------------------
// Channel::activeChannel
//---------------------------------------------------------
Ms::Channel* Channel::activeChannel()
{
Ms::Score* score = _part->score();
Ms::MasterScore* masterScore = score->masterScore();
if (masterScore->playbackScore() == score) {
return masterScore->playbackChannel(_channel);
}
return _channel;
}
//---------------------------------------------------------
// Channel::setMidiBankAndProgram
//---------------------------------------------------------
void Channel::setMidiBankAndProgram(int bank, int program, bool setUserBankController)
{
Ms::Channel* ch = activeChannel();
MidiPatch patch;
// other values are unused in ChangePatch command
patch.synti = ch->synti();
patch.bank = bank;
patch.prog = program;
Ms::Score* score = _part->score();
score->undo(new ChangePatch(score, ch, &patch));
if (setUserBankController) {
score->undo(new SetUserBankController(ch, true));
}
}
//---------------------------------------------------------
// Channel::setMidiProgram
//---------------------------------------------------------
void Channel::setMidiProgram(int prog)
{
prog = qBound(0, prog, 127);
setMidiBankAndProgram(activeChannel()->bank(), prog, false);
}
//---------------------------------------------------------
// Channel::setMidiBank
//---------------------------------------------------------
void Channel::setMidiBank(int bank)
{
bank = qBound(0, bank, 255);
setMidiBankAndProgram(bank, activeChannel()->program(), true);
}
//---------------------------------------------------------
// StringData::stringList
//---------------------------------------------------------
QVariantList StringData::stringList() const
{
QVariantList pluginStringsList;
for (instrString str : _data.stringList()) {
QVariantMap pluginStringData;
pluginStringData["pitch"] = str.pitch;
pluginStringData["open"] = str.open;
pluginStringsList.append(pluginStringData);
}
return pluginStringsList;
}
//---------------------------------------------------------
// ChannelListProperty
//---------------------------------------------------------
ChannelListProperty::ChannelListProperty(Instrument* i)
: QQmlListProperty<Channel>(i, i, &count, &at) {}
//---------------------------------------------------------
// ChannelListProperty::count
//---------------------------------------------------------
int ChannelListProperty::count(QQmlListProperty<Channel>* l)
{
return static_cast<int>(static_cast<Instrument*>(l->data)->instrument()->channel().size());
}
//---------------------------------------------------------
// ChannelListProperty::at
//---------------------------------------------------------
Channel* ChannelListProperty::at(QQmlListProperty<Channel>* l, int i)
{
Instrument* instr = static_cast<Instrument*>(l->data);
if (i < 0 || i >= instr->instrument()->channel().size()) {
return nullptr;
}
Ms::Channel* ch = instr->instrument()->channel(i);
return customWrap<Channel>(ch, instr->part());
}
//---------------------------------------------------------
// Instrument::longName
//---------------------------------------------------------
QString Instrument::longName() const
{
const QList<Ms::StaffName>& names = instrument()->longNames();
return names.empty() ? "" : names[0].name();
}
//---------------------------------------------------------
// Instrument::shortName
//---------------------------------------------------------
QString Instrument::shortName() const
{
const QList<Ms::StaffName>& names = instrument()->shortNames();
return names.empty() ? "" : names[0].name();
}
//---------------------------------------------------------
// Instrument::channels
//---------------------------------------------------------
ChannelListProperty Instrument::channels()
{
return ChannelListProperty(this);
}
} // namespace PluginAPI
} // namespace Ms

View file

@ -0,0 +1,265 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#ifndef __PLUGIN_API_INSTRUMENT_H__
#define __PLUGIN_API_INSTRUMENT_H__
#include "scoreelement.h"
#include "libmscore/instrument.h"
namespace Ms {
class Instrument;
namespace PluginAPI {
class Instrument;
//---------------------------------------------------------
// Channel
/// Provides an access to channel properties. Similar to
/// Mixer, changes to some of the playback-related
/// properties are not recorded to undo stack and are not
/// revertable with standard user-visible "undo" action.
/// Changing MIDI patch though is undoable in normal way
/// ("dock" type plugins may need to call
/// \ref Score.startCmd / \ref Score.endCmd for that to
/// work properly).
///
/// Iterating over all channels in the current score can
/// be done as follows:
/// \code
/// var parts = curScore.parts;
/// for (var i = 0; i < parts.length; ++i) {
/// var part = parts[i];
/// var instrs = part.instruments;
/// for (var j = 0; j < instrs.length; ++j) {
/// var instr = instrs[j];
/// var channels = instr.channels;
/// for (var k = 0; k < channels.length; ++k) {
/// var channel = channels[k];
/// channel.volume = 64; // just for example, changing the channel's volume
/// }
/// }
/// }
/// \endcode
/// \since MuseScore 3.5
//---------------------------------------------------------
class Channel : public QObject
{
Q_OBJECT
Ms::Channel* _channel;
Ms::Part* _part;
/** Name of this channel */
Q_PROPERTY(QString name READ name)
/**
* Channel volume, from 0 to 127.
* \note Changing this property is **not** revertable with a standard "undo"
* action. Plugins may need to handle reverting this property change if
* necessary.
*/
Q_PROPERTY(int volume READ volume WRITE setVolume)
/**
* Channel pan, from 0 to 127.
* \note Changing this property is **not** revertable with a standard "undo"
* action. Plugins may need to handle reverting this property change if
* necessary.
*/
Q_PROPERTY(int pan READ pan WRITE setPan)
/**
* Channel chorus, from 0 to 127.
* \note Changing this property is **not** revertable with a standard "undo"
* action. Plugins may need to handle reverting this property change if
* necessary.
*/
Q_PROPERTY(int chorus READ chorus WRITE setChorus)
/**
* Channel reverb, from 0 to 127.
* \note Changing this property is **not** revertable with a standard "undo"
* action. Plugins may need to handle reverting this property change if
* necessary.
*/
Q_PROPERTY(int reverb READ reverb WRITE setReverb)
/**
* Whether this channel is muted.
* \note Changing this property is **not** revertable with a standard "undo"
* action. Plugins may need to handle reverting this property change if
* necessary.
*/
Q_PROPERTY(bool mute READ mute WRITE setMute)
/**
* MIDI program number, from 0 to 127. Changing this property is recorded
* to the program's undo stack and can be reverted with a standard "undo"
* action.
*/
Q_PROPERTY(int midiProgram READ midiProgram WRITE setMidiProgram)
/**
* MIDI patch bank number. Changing this property is recorded
* to the program's undo stack and can be reverted with a standard "undo"
* action.
*/
Q_PROPERTY(int midiBank READ midiBank WRITE setMidiBank)
Ms::Channel* activeChannel();
void setMidiBankAndProgram(int bank, int program, bool setUserBankController);
public:
/// \cond MS_INTERNAL
Channel(Ms::Channel* ch, Ms::Part* p, QObject* parent = nullptr)
: QObject(parent), _channel(ch), _part(p) {}
QString name() const { return _channel->name(); }
int volume() const { return _channel->volume(); }
void setVolume(int val) { activeChannel()->setVolume(qBound(0, val, 127)); }
int pan() const { return _channel->pan(); }
void setPan(int val) { activeChannel()->setPan(qBound(0, val, 127)); }
int chorus() const { return _channel->chorus(); }
void setChorus(int val) { activeChannel()->setChorus(qBound(0, val, 127)); }
int reverb() const { return _channel->reverb(); }
void setReverb(int val) { activeChannel()->setReverb(qBound(0, val, 127)); }
bool mute() const { return _channel->mute(); }
void setMute(bool val) { activeChannel()->setMute(val); }
int midiProgram() const { return _channel->program(); }
void setMidiProgram(int prog);
int midiBank() const { return _channel->bank(); }
void setMidiBank(int bank);
/// \endcond
};
//---------------------------------------------------------
// StringData
/// \since MuseScore 3.5
//---------------------------------------------------------
class StringData : public QObject
{
Q_OBJECT
/**
* List of strings in this instrument.
* \returns A list of objects representing strings. Each
* object has the following fields:
* - \p pitch - pitch of this string on fret 0 (integer).
* - \p open - if \p true, this string is not fretted and
* always open. If \p false, the string **is**
* fretted. For example, for classical guitar
* all strings are fretted, and for them
* \p open value will always be \p false.
*/
Q_PROPERTY(QVariantList strings READ stringList)
/** Number of frets in this instrument */
Q_PROPERTY(int frets READ frets)
Ms::StringData _data;
public:
/// \cond MS_INTERNAL
StringData(const Ms::StringData* d, QObject* parent = nullptr)
: QObject(parent), _data(*d) {}
QVariantList stringList() const;
int frets() const { return _data.frets(); }
/// \endcond
};
//---------------------------------------------------------
// ChannelListProperty
/// \cond PLUGIN_API \private \endcond
//---------------------------------------------------------
class ChannelListProperty : public QQmlListProperty<Channel>
{
public:
ChannelListProperty(Instrument* i);
static int count(QQmlListProperty<Channel>* l);
static Channel* at(QQmlListProperty<Channel>* l, int i);
};
//---------------------------------------------------------
// Instrument
/// \since MuseScore 3.5
//---------------------------------------------------------
class Instrument : public QObject
{
Q_OBJECT
/**
* The string identifier
* ([MusicXML Sound ID](https://www.musicxml.com/for-developers/standard-sounds/))
* for this instrument.
* \see \ref Ms::PluginAPI::Part::instrumentId "Part.instrumentId"
*/
Q_PROPERTY(QString instrumentId READ instrumentId)
// Ms::Instrument supports multiple short/long names (for aeolus instruments?)
// but in practice only one is actually used. If this gets changed this API could
// be expanded.
/** The long name for this instrument. */
Q_PROPERTY(QString longName READ longName)
/** The short name for this instrument. */
Q_PROPERTY(QString shortName READ shortName)
/**
* For fretted instruments, an information about this
* instrument's strings.
*/
Q_PROPERTY(Ms::PluginAPI::StringData* stringData READ stringData)
// TODO: a property for drumset?
Q_PROPERTY(QQmlListProperty<Ms::PluginAPI::Channel> channels READ channels)
Ms::Instrument* _instrument;
Ms::Part* _part;
public:
/// \cond MS_INTERNAL
Instrument(Ms::Instrument* i, Ms::Part* p)
: QObject(), _instrument(i), _part(p) {}
Ms::Instrument* instrument() { return _instrument; }
const Ms::Instrument* instrument() const { return _instrument; }
Ms::Part* part() { return _part; }
QString instrumentId() const { return instrument()->instrumentId(); }
QString longName() const;
QString shortName() const;
StringData* stringData() { return customWrap<StringData>(instrument()->stringData()); }
ChannelListProperty channels();
/// \endcond
/** Checks whether two variables represent the same object. */
Q_INVOKABLE bool is(Ms::PluginAPI::Instrument* other) { return other && instrument() == other->instrument(); }
};
} // namespace PluginAPI
} // namespace Ms
#endif

77
src/plugins/api/part.cpp Normal file
View file

@ -0,0 +1,77 @@
//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2020 MuseScore BVBA
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "part.h"
#include "instrument.h"
namespace Ms {
namespace PluginAPI {
//---------------------------------------------------------
// InstrumentListProperty
//---------------------------------------------------------
InstrumentListProperty::InstrumentListProperty(Part* p)
: QQmlListProperty<Instrument>(p, p, &count, &at) {}
//---------------------------------------------------------
// InstrumentListProperty::count
//---------------------------------------------------------
int InstrumentListProperty::count(QQmlListProperty<Instrument>* l)
{
return static_cast<int>(static_cast<Part*>(l->data)->part()->instruments()->size());
}
//---------------------------------------------------------
// InstrumentListProperty::at
//---------------------------------------------------------
Instrument* InstrumentListProperty::at(QQmlListProperty<Instrument>* l, int i)
{
Part* part = static_cast<Part*>(l->data);
const Ms::InstrumentList* il = part->part()->instruments();
if (i < 0 || i >= int(il->size())) {
return nullptr;
}
Ms::Instrument* instr = std::next(il->begin(), i)->second;
return customWrap<Instrument>(instr, part->part());
}
//---------------------------------------------------------
// Part::instruments
//---------------------------------------------------------
InstrumentListProperty Part::instruments()
{
return InstrumentListProperty(this);
}
//---------------------------------------------------------
// Part::instrumentAtTick
//---------------------------------------------------------
Instrument* Part::instrumentAtTick(int tick)
{
return customWrap<Instrument>(part()->instrument(Ms::Fraction::fromTicks(tick)), part());
}
} // namespace PluginAPI
} // namespace Ms

View file

@ -18,6 +18,23 @@
namespace Ms {
namespace PluginAPI {
class Instrument;
class Part;
//---------------------------------------------------------
// InstrumentListProperty
/// \cond PLUGIN_API \private \endcond
//---------------------------------------------------------
class InstrumentListProperty : public QQmlListProperty<Instrument>
{
public:
InstrumentListProperty(Part* p);
static int count(QQmlListProperty<Instrument>* l);
static Instrument* at(QQmlListProperty<Instrument>* l, int i);
};
//---------------------------------------------------------
// Part
//---------------------------------------------------------
@ -68,6 +85,12 @@ class Part : public Ms::PluginAPI::ScoreElement
/// \since MuseScore 3.2.1
Q_PROPERTY(bool show READ show)
/**
* List of instruments in this part.
* \since MuseScore 3.5
*/
Q_PROPERTY(QQmlListProperty<Ms::PluginAPI::Instrument> instruments READ instruments);
public:
/// \cond MS_INTERNAL
Part(Ms::Part* p = nullptr, Ownership o = Ownership::SCORE)
@ -90,7 +113,15 @@ public:
QString shortName() const { return part()->shortName(); }
QString partName() const { return part()->partName(); }
bool show() const { return part()->show(); }
InstrumentListProperty instruments();
/// \endcond
/**
* Finds an instrument that is active in this part at the given \p tick.
* \since MuseScore 3.5
*/
Q_INVOKABLE Ms::PluginAPI::Instrument* instrumentAtTick(int tick);
};
} // namespace PluginAPI
} // namespace Ms

View file

@ -14,6 +14,7 @@
#include "cursor.h"
#include "elements.h"
#include "fraction.h"
#include "instrument.h"
#include "score.h"
#include "part.h"
#include "util.h"
@ -352,6 +353,9 @@ void PluginAPI::registerQmlTypes()
qmlRegisterAnonymousType<Segment>("MuseScore", 3);
qmlRegisterAnonymousType<Measure>("MuseScore", 3);
qmlRegisterAnonymousType<Part>("MuseScore", 3);
qmlRegisterAnonymousType<Instrument>("MuseScore", 3);
qmlRegisterAnonymousType<Channel>("MuseScore", 3);
qmlRegisterAnonymousType<StringData>("MuseScore", 3);
qmlRegisterAnonymousType<Excerpt>("MuseScore", 3);
qmlRegisterAnonymousType<Selection>("MuseScore", 3);
qmlRegisterAnonymousType<Tie>("MuseScore", 3);

View file

@ -107,6 +107,24 @@ Wrapper* wrap(T* t, Ownership own = Ownership::SCORE)
extern ScoreElement* wrap(Ms::ScoreElement* se, Ownership own = Ownership::SCORE);
//---------------------------------------------------------
// customWrap
/// \cond PLUGIN_API \private \endcond
/// \internal
/// Can be used to construct wrappers which do not
/// support standard ownership logic or require
/// additional arguments for initialization.
//---------------------------------------------------------
template<class Wrapper, class T, typename ... Args>
Wrapper* customWrap(T* t, Args... args)
{
Wrapper* w = t ? new Wrapper(t, std::forward<Args>(args)...) : nullptr;
// All wrapper objects should belong to JavaScript code.
QQmlEngine::setObjectOwnership(w, QQmlEngine::JavaScriptOwnership);
return w;
}
//---------------------------------------------------------
/// QML access to containers.
/// A wrapper which provides read-only access for various