From 9d3c8c0e6e1a7ba43bf3dc19350d1dca68b657a3 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Sun, 10 Feb 2019 11:16:07 +0800 Subject: Initial commit. --- QMidiPlayer/doc/APIdoc.md | 217 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 QMidiPlayer/doc/APIdoc.md (limited to 'QMidiPlayer/doc/APIdoc.md') diff --git a/QMidiPlayer/doc/APIdoc.md b/QMidiPlayer/doc/APIdoc.md new file mode 100644 index 0000000..be7bbc5 --- /dev/null +++ b/QMidiPlayer/doc/APIdoc.md @@ -0,0 +1,217 @@ +# The API documentation of QMidiPlayer for plugin developers + +*This manual is not yet complete. It's only a working draft for the always-changing plugin system in QMP.* +*Handle with care.* + +# 0. Overview + +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. + +# 1. "QMidiPlayer Plugin SDK" + +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. + +# 2. Basics for a working plugin + +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: + +```C++ +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`. + +# 3. A peek into the class `qmpPluginIntf` + +It has 6 public members: one default constructor, one default destructor and four methods: + +- `void init()` + Called on start up if the plugin is loaded successfully and enabled. +- `void deinit()` + Called on shutdown if the plugin is enabled. +- `const char* pluginGetName()` + This function should return the display name of the plugin. +- `const char* pluginGetVersion()` + This function should return the version of the plugin, which will be shown in the plugin manager. + +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: + +- Visualization (via `(un)registerVisualizationIntf`) +- MIDI File Reader (via `(un)registerFileReader`) + +...and can hooks into the following processes: + +- Event reader, after an event is read or after the whole file reading process is completed + (via `(un)registerEventReaderIntf` and `(un)registerFileReadFinishedHandlerIntf`) +- Event handler, when an event is going to be sent by the player (via `(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`). + +# 4. Functionalities +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! + +## 4.1 What is a functionality? +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. + +## 4.2 Visualization +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. + +## 4.3 MIDI File Reader +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! + +## 4.4 MIDI Output Device +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. + +# 5. Generic Considerations + +1. If you implemented a API that returns a pointer to something, you can forget about the pointer after returning +it. The core will free its memory after it is no longer used. You shouldn't extend the class pointed to because +if you do so, the core will not be able to destruct it correctly. Examples include IMidiFileReader::readFile which +returns a pointer to a CMidiFile class. +2. However if you passed a pointer to the core through a function in qmpPluginAPI, you cannot forget about the pointer +later. As these pointers are mostly polymorphic, the core cannot handle their destruction. You have to delete them +yourself in the deinit() function of your plugin. +3. Do not throw exceptions to the core. The core doesn't handle exceptions and they will crash the entire program. +Use the return value to indicate failure of a procedure instead. + +# 6. Reference +Well, everything above is just nonsense compared to this one. The full reference of the API is here. + +## Structures & Classes + +### struct `SEvent` +Describes an MIDI event. + +- `uint32_t iid` +internal id. Usually set to the size of the event pool when current event is read. +- `uint32_t time` +timestamp of the event in MIDI tick. +- `uint32_t p1` +parameter 1 for the midi event. +- `uint32_t p2` +parameter 2 for the midi event. +- `uint8_t type` +type of the event together with the channel this event goes to. +- `std::string str` +Contains the raw data for string-like events. +- default constructor: `SEvent()` +sets everything to zero or empty. +- constructor with parameters: `SEvent(uint32_t _iid,uint32_t _t,char _tp,uint32_t _p1,uint32_t _p2,const char* s=NULL)` +fills the event with the parameters given. +- `friend bool operator <(const SEvent& a,const SEvent& b)` +compares events by their timestamps. Ties are broken by comparing precedence in file. + +### struct `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` + +### class `CMidiTrack` +Describes a single MIDI track. A MIDI track consists of multiple MIDI events. + +- `std::vector eventList` +Vector of SEvent's. +- `void appendEvent(SEvent e)` +Append an event to the end of the event list. +- `SEvent& operator[](size_t sub)` +Get the reference to the contained event with the given index. + +### class `CMidiFile` +Describes a MIDI file. A MIDI file consists of multiple MIDI tracks. + +- `bool valid` +Is the MIDI file a valid one? +- `char* title` +Title of the MIDI file. +- `char* copyright` +Copyright information of the MIDI file. +- `std::vector tracks` +Tracks in the MIDI file. +- `uint32_t std` +File standard of the MIDI file. + - 0: unknown + - 1: GM Level 1 + - 2: GM Level 2 + - 3: GS + - 4: XG +- `uint32_t div` +Ticks per quarter note. SMTPE format is not supported by QMidiPlayer. +- `~CMidiFile()` +Frees memory occupied by the title and copyright string. + +### class `IMidiCallBack` +Generic callback function that can be used for hooking the core. + +- `IMidiCallBack()` +Default empty constructor. +- `virtual void callBack(void* callerdata,void* userdata)=0;` +Abstract function of the callback. callerdata is filled by the caller and +userdata is set to whatever you asked for when registering the callback. +- `virtual ~IMidiCallBack()` +Virtual empty destructor. + +### class `IMidiFileReader` +MIDI file reader interface. Use this to implement your file importer. + +- `IMidiFileReader()` +Default empty constructor. +- `virtual ~IMidiFileReader()` +Virtual empty destructor. +- `virtual CMidiFile* readFile(const char* fn)=0` +Abstract function to be implemented by the plugin. +This function should read file from the given path (fn) and return a +pointer to the resulting CMidiFile structure. You shoudn't handle the +destruction of the resulting structure as the core will handle it. +Read 5.1 for more details. +After reading each event, you should call qmpPluginAPI::callEventReaderCB +to invoke event reader callbacks and process their requests. +If a file not supported by the reader is provided, this function should +return a CMidiFile* whose `valid` field is `false`. +- `virtual void discardCurrentEvent()=0` +Only called by event reader callbacks. +Expected behavior: Sets the discard flag for the last event you have read. +If an event has its discard flag set, it shouldn't be pushed into its track. +discardCurrentEvent may be called multiple times by different event reader +callbacks. In such case, there's still only one event discarded. +- `virtual void commitEventChange(SEventCallBackData d)=0` +Only called by event reader callbacks. +Expected behavior: modifies the last event you have read to the provided data. +commitEventChange may be called multiple times by different event reader callbacks. +In such case, only the last modification to the current event counts. -- cgit v1.2.3