This manual is not yet complete. It's only a working draft for the always-changing plugin system in QMP. Handle with care.
Plugin for QMidiPlayer is a dynamically-loaded library that exports the symbol qmpPluginGetInterface
and qmpPluginGetAPIRev
. Before starting developing your own plugin, make sure to have a look at the sample plugin in the "sample-plugin" folder.
SDK for developing QMidiPlayer plugins is merely the qmpcorepublic.hpp
header found in the "include" directory in the source tree. It includes classes used by QMidiPlayer's internal plugin infrastructure.
First of all, you should make your library distinct from other libraries that are not QMidiPlayer plugins. You can achive it by exporting the symbols qmpPluginGetInterface
and qmpPluginGetAPIRev
. Specifically, what you should do is to add the following snipplet to somewhere of your code:
extern "C"{
EXPORTSYM qmpPluginIntf* qmpPluginGetInterface(qmpPluginAPI* api)
//semicolon or implementation here.
EXPORTSYM const char* qmpPluginGetAPIRev()
{return QMP_PLUGIN_API_REV;}
}
The EXPORTSYM
macro tells the compiler to export the following symbol. qmpPluginIntf
is the abstract class which every plugin class should derive from. The parameter api provides access to QMidiPlayer's plugin API, which should be stored for future use. qmpPluginGetAPIRev
helps the core to determine whether the plugin is compatible with the API it exports.
Next you should create your own plugin class which implements the abstract class qmpPluginIntf
.
qmpPluginIntf
It has 6 public members: one default constructor, one default destructor and four methods:
void init()
void deinit()
const char* pluginGetName()
const char* pluginGetVersion()
Your plugin is expected to register handlers (hooks) and functionalities when init() is called by the host, and do clean-up jobs when deinit() is caled.
Currently plugins can register handlers for these functionalities:
(un)registerVisualizationIntf
)(un)registerFileReader
)...and can hooks into the following processes:
(un)registerEventReaderIntf
and (un)registerFileReadFinishedHandlerIntf
)(un)registerEventHandlerIntf
)Functionalities has their own interfaces you need to implement(qmpVisualizationIntf
and IMidiFileReader
, respectively), while hooks uses the universal IMidiCallBack
interface. Functionalities are discussed later.
When you register a hook, you provide the core with a instance of your class that implements the IMidiCallBack
interface and your userdata
to be used when the core is calling the callback. When the callback is called, it will be fed with proper callerdata
generated by the core and the userdata
you provided. Type of callerdata
varies by hooks. Event reader and handler hooks have SEventCallBackData*
as their callerdata
while file read finish hook doesn't provide callerdata
(NULL
).
Plugins extend the host with extra functionalities. With hooks, handlers and the built-in core API, you can already do a lot of hacking. If that cannot make you satisfied, QMidiPlayer have several vacancies that are expected to be implemented by plugins. And with the introduction of the general functionality API, you can now virtually add anything to QMidiPlayer!
Have a look at the main window. By default there're three or four buttons at the bottom of it. These are functionalities. Functionalties go into two types: checkable and non-checkable. Checkable functionalities can be toggled on or off, while non-checkable functionalities have no such states. For non-checkable functionalities, only show() is called when the user invokes it. The user can arrange functionalities shown on the toolbar and the action menu to their needs.
Visualization was once a feature of QMidiPlayer's core. But you can now write your own visualization with the Visualization interface(qmpVisualizationIntf
). The methods in this interface should be self-explanatory.
This is not strictly a "functionality", because its interface IMidiFileReader does not inherit qmpFuncBaseIntf. When the user requests to open a file, the core tries to load the file with registered file readers and accepts the first valid result. Therefore you can implement your own file reader, which may even add eXtended Module support to QMidiPlayer!
This is not a functionality either. By implementing the interface qmpMidiOutDevice and registering it, you can add custom MIDI Devices in the built-in MIDI mapper.
Well, everything above is just nonsense compared to this one. The full reference of the API is here.
SEvent
Describes an MIDI event.
uint32_t iid
uint32_t time
uint32_t p1
uint32_t p2
uint8_t type
std::string str
SEvent()
SEvent(uint32_t _iid,uint32_t _t,char _tp,uint32_t _p1,uint32_t _p2,const char* s=NULL)
friend bool operator <(const SEvent& a,const SEvent& b)
SEventCallBackData
A stripped down version of SEvent that is used to pass event data in APIs.
uint32_t time
uint32_t type
uint32_t p1
uint32_t p2
CMidiTrack
Describes a single MIDI track. A MIDI track consists of multiple MIDI events.
std::vector<SEvent> eventList
void appendEvent(SEvent e)
SEvent& operator[](size_t sub)
CMidiFile
Describes a MIDI file. A MIDI file consists of multiple MIDI tracks.
bool valid
char* title
char* copyright
std::vector<CMidiTrack> tracks
Tracks in the MIDI file.uint32_t std
uint32_t div
~CMidiFile()
IMidiCallBack
Generic callback function that can be used for hooking the core.
IMidiCallBack()
virtual void callBack(void* callerdata,void* userdata)=0;
virtual ~IMidiCallBack()
IMidiFileReader
MIDI file reader interface. Use this to implement your file importer.
IMidiFileReader()
virtual ~IMidiFileReader()
virtual CMidiFile* readFile(const char* fn)=0
valid
field is false
.virtual void discardCurrentEvent()=0
virtual void commitEventChange(SEventCallBackData d)=0