From 1976c49f835267d33ef88bd3fc20d18363e12c0b Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Wed, 8 Feb 2017 23:45:18 +0800 Subject: Add API version verification. This breaks compatibility with old versions of plugins. Add RIFF MIDI support to the SMF reader. Documentation. --- ChangeLog | 6 ++++ core/qmpmidiplay.hpp | 1 + core/qmpmidiread.cpp | 13 ++++++- doc/APIdoc.md | 65 ++++++++++++++++++++++++---------- doc/version.html | 2 +- include/qmpcorepublic.hpp | 10 +++--- qmidiplayer-desktop/qmpplistwindow.cpp | 4 +-- qmidiplayer-desktop/qmpplugin.cpp | 10 ++++++ qmidiplayer-desktop/qmpplugin.hpp | 1 + sample-plugin/sampleplugin.hpp | 2 ++ visualization/qmpvisualization.hpp | 2 ++ 11 files changed, 90 insertions(+), 26 deletions(-) diff --git a/ChangeLog b/ChangeLog index a3c82b3..c66874f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2017-02-08 0.8.5 indev +Add API version verification. This breaks compatibility +with old versions of plugins. +Documentation. +Add RIFF MIDI support to the SMF reader. + 2017-02-07 0.8.5 indev First steps for the file reader API. API additions and changes. diff --git a/core/qmpmidiplay.hpp b/core/qmpmidiplay.hpp index 8d78c4e..b911844 100644 --- a/core/qmpmidiplay.hpp +++ b/core/qmpmidiplay.hpp @@ -6,6 +6,7 @@ #include #include #include +#define QMP_MAIN #include "../include/qmpcorepublic.hpp" #include "qmpmidimappers.hpp" class CMidiPlayer; diff --git a/core/qmpmidiread.cpp b/core/qmpmidiread.cpp index 4ac2ac8..b36fffc 100644 --- a/core/qmpmidiread.cpp +++ b/core/qmpmidiread.cpp @@ -203,10 +203,21 @@ void CSMFReader::headerChunkReader() int CSMFReader::chunkReader(int hdrXp) { char hdr[6]; - if(!fgets(hdr,5,f))error(1,"E: Unexpected EOF."); + fread(hdr,1,4,f); + if(feof(f))error(1,"E: Unexpected EOF."); if(hdrXp) + { + if(!strncmp(hdr,"RIFF",4)) + { + fseek(f,4,SEEK_CUR); + fread(hdr,1,4,f); + if(strncmp(hdr,"RMID",4)){error(1,"E: Wrong file type in RIFF container.");throw std::runtime_error("Wrong file type in RIFF container");} + fseek(f,8,SEEK_CUR); + fread(hdr,1,4,f); + } if(strncmp(hdr,"MThd",4)){error(1,"E: Wrong MIDI header.");throw std::runtime_error("Wrong MIDI header");} else return headerChunkReader(),0; + } else if(strncmp(hdr,"MTrk",4)) { diff --git a/doc/APIdoc.md b/doc/APIdoc.md index 043c1c0..5b69b5e 100644 --- a/doc/APIdoc.md +++ b/doc/APIdoc.md @@ -5,43 +5,72 @@ # 0. Overview -Plugin for QMidiPlayer is a dynamically-loaded library that exports the symbol "qmpPluginGetInterface". +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 +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. +# 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 symbol "qmpPluginGetInterface". Specifically, what you should do is to add the following snipplet to -somewhere of your code: +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. -> } +``` +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 +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. +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". +Next you should create your own plugin class which implements the abstract class `qmpPluginIntf`. -# 3. A Peek into the 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() +- `void init()` Called on start up if the plugin is loaded successfully and enabled. -- void deinit() +- `void deinit()` Called on shutdown if the plugin is enabled. -- const char* pluginGetName() +- `const char* pluginGetName()` This function should return the display name of the plugin. -- const char* pluginGetVersion() +- `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 and functionalities when init() is called by the host, +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 hosts 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. diff --git a/doc/version.html b/doc/version.html index 93bd344..d3b3218 100644 --- a/doc/version.html +++ b/doc/version.html @@ -26,7 +26,7 @@

Version information


- QMidiPlayer documentation for version 0.8.2
+ QMidiPlayer documentation for version 0.8.5
An MIDI player based on fluidsynth and Qt.
Written by Chris Xiong.

diff --git a/include/qmpcorepublic.hpp b/include/qmpcorepublic.hpp index 6e56310..f42d42a 100644 --- a/include/qmpcorepublic.hpp +++ b/include/qmpcorepublic.hpp @@ -1,6 +1,5 @@ -#ifndef QMPCOREPUBLIC_H -#define QMPCOREPUBLIC_H -#include +#ifndef QMPCOREPUBLIC_HPP +#define QMPCOREPUBLIC_HPP #include #include #include @@ -9,6 +8,7 @@ #else #define EXPORTSYM __attribute__ ((visibility ("default"))) #endif +#define QMP_PLUGIN_API_REV "1+indev" //MIDI Event structure struct SEvent { @@ -162,4 +162,6 @@ class qmpPluginAPI //through the parameter. This function should return a pointer to a class //that implementes the plugin pinterface (qmpPluginIntf). typedef qmpPluginIntf*(*qmpPluginEntry)(qmpPluginAPI*); -#endif // QMPCOREPUBLIC_H +//The following symbol only presents in plugins. Its purpose is to help the core reject incompatible plugins. +typedef const char*(*qmpPluginAPIRevEntry)(); +#endif // QMPCOREPUBLIC_HPP diff --git a/qmidiplayer-desktop/qmpplistwindow.cpp b/qmidiplayer-desktop/qmpplistwindow.cpp index 75e2e44..5805fcb 100644 --- a/qmidiplayer-desktop/qmpplistwindow.cpp +++ b/qmidiplayer-desktop/qmpplistwindow.cpp @@ -149,7 +149,7 @@ void qmpPlistWindow::on_pbAdd_clicked() if(qmpSettingsWindow::getSettingsIntf()->value("Behavior/DialogStatus","").toInt()) sl=QFileDialog::getOpenFileNames(this,"Add File",qmpSettingsWindow::getSettingsIntf()->value("DialogStatus/FileDialogPath","").toString(),"Midi files (*.mid *.midi)"); else - sl=QFileDialog::getOpenFileNames(this,"Add File","","Midi files (*.mid *.midi)"); + sl=QFileDialog::getOpenFileNames(this,"Add File","","Midi files (*.mid *.midi *.rmi)"); if(sl.empty())return; for(int i=0;ilwFiles->addItem(new QListWidgetItem(c)); } } diff --git a/qmidiplayer-desktop/qmpplugin.cpp b/qmidiplayer-desktop/qmpplugin.cpp index 5f5d82e..96936bd 100644 --- a/qmidiplayer-desktop/qmpplugin.cpp +++ b/qmidiplayer-desktop/qmpplugin.cpp @@ -31,6 +31,11 @@ void qmpPluginManager::scanPlugins() if(!hso){fprintf(stderr,"Error while loading library: %d\n",GetLastError());continue;} FARPROC hndi=GetProcAddress(hso,"qmpPluginGetInterface"); if(!hndi){fprintf(stderr,"file %s doesn't seem to be a qmidiplayer plugin.\n",cpluginpaths[i].c_str());continue;} + FARPROC hndiv=GetProcAddress(hso,"qmpPluginGetAPIRev"); + if(!hndiv){fprintf(stderr,"file %s is incompatible with this version of qmidiplayer.\n",cpluginpaths[i].c_str());continue;} + qmpPluginAPIRevEntry getv=(qmpPluginAPIRevEntry)hndiv; + if(strcmp(getv(),QMP_PLUGIN_API_REV)) + {fprintf(stderr,"file %s is incompatible with this version of qmidiplayer.\n",cpluginpaths[i].c_str());continue;} qmpPluginEntry e=(qmpPluginEntry)hndi; qmpPluginIntf* intf=e(pluginAPI); plugins.push_back(qmpPlugin(std::string(intf->pluginGetName()),std::string(intf->pluginGetVersion()),std::string(cpluginpaths[i]),intf)); @@ -65,6 +70,11 @@ void qmpPluginManager::scanPlugins() if(!hso){fprintf(stderr,"%s\n",dlerror());continue;} void* hndi=dlsym(hso,"qmpPluginGetInterface"); if(!hndi){fprintf(stderr,"file %s doesn't seem to be a qmidiplayer plugin.\n",cpluginpaths[i].c_str());continue;} + void* hndiv=dlsym(hso,"qmpPluginGetAPIRev"); + if(!hndiv){fprintf(stderr,"file %s is incompatible with this version of qmidiplayer.\n",cpluginpaths[i].c_str());continue;} + qmpPluginAPIRevEntry getv=(qmpPluginAPIRevEntry)hndiv; + if(strcmp(getv(),QMP_PLUGIN_API_REV)) + {fprintf(stderr,"file %s is incompatible with this version of qmidiplayer.\n",cpluginpaths[i].c_str());continue;} qmpPluginEntry e=(qmpPluginEntry)hndi; qmpPluginIntf* intf=e(pluginAPI); plugins.push_back(qmpPlugin(std::string(intf->pluginGetName()),std::string(intf->pluginGetVersion()),std::string(cpluginpaths[i]),intf)); diff --git a/qmidiplayer-desktop/qmpplugin.hpp b/qmidiplayer-desktop/qmpplugin.hpp index d69adeb..512fc35 100644 --- a/qmidiplayer-desktop/qmpplugin.hpp +++ b/qmidiplayer-desktop/qmpplugin.hpp @@ -3,6 +3,7 @@ #define QMP_MAIN #include #include +#define QMP_MAIN #include "../include/qmpcorepublic.hpp" struct qmpPlugin { diff --git a/sample-plugin/sampleplugin.hpp b/sample-plugin/sampleplugin.hpp index a47e037..ffb3d77 100644 --- a/sample-plugin/sampleplugin.hpp +++ b/sample-plugin/sampleplugin.hpp @@ -19,6 +19,8 @@ class qmpSamplePlugin:public qmpPluginIntf extern "C"{ EXPORTSYM qmpPluginIntf* qmpPluginGetInterface(qmpPluginAPI* api) {return new qmpSamplePlugin(api);} + EXPORTSYM const char* qmpPluginGetAPIRev() + {return QMP_PLUGIN_API_REV;} } #endif // SAMPLEPLUGIN_H diff --git a/visualization/qmpvisualization.hpp b/visualization/qmpvisualization.hpp index 6a66785..28e9b52 100644 --- a/visualization/qmpvisualization.hpp +++ b/visualization/qmpvisualization.hpp @@ -133,6 +133,8 @@ class CDemoVisualization:public qmpVisualizationIntf extern "C"{ EXPORTSYM qmpPluginIntf* qmpPluginGetInterface(qmpPluginAPI* api) {return new qmpVisualization(api);} + EXPORTSYM const char* qmpPluginGetAPIRev() + {return QMP_PLUGIN_API_REV;} } #endif // QMPVISUALIZATION_H -- cgit v1.2.3