mxPlay Programmer's Plugin API Reference
Writing any plugin-based application is always very tricky. You have to provide some universal, in the future expandable, easy to understand and, at last but not least, working interface between 'server' application and 'client' plugin.
My goal was to remove as much work from programmer as possible. I think this approach is not only good for programmer but it increases overall stability since application has control over more things.
All mentioned constants and structures are defined in the file MXP_INC.S.
Each plugin should be compiled as normal .PRG binary, for example from Devpac.
2.1 Header
Offsets are relative to the begin of text segment. So it's very useful to create some label at the begin of your code and to use predefined offets.
For example:
ogg_header:
  dc.l   "MXP1"
  ds.l   1
  bra.w  register_module
  :
  :
and then access to the members using:
lea    ogg_header,a0
move.l d0,(a0,MXP_PLUGIN_PARAMETER)
or:
move.l d0,ogg_header+MXP_PLUGIN_PARAMETER
Please note all entries are 4 bytes long.
2.1.1 MXP_PLUGIN_ID
Characters 'MXP1', i.e. ASCII identifier.
2.1.2 MXP_PLUGIN_PARAMETER
Storage place for both application and plugin. This is the only place where they are allowed to write. For more about this, see chapter 4.2 'Communication Issues'. The content can be either constant or pointer, depending on the passed data, see discussion about structures bellow.
I didn't want to use register-based communication since every C compiler passes parameters on stack differently and I wanted to do portable code.
2.1.3 MXP_PLUGIN_REGISTER_MOD
Relative jump to Register() function.
2.1.4 MXP_PLUGIN_PLAYTIME
Relative jump to Playtime() function.
2.1.5 MXP_PLUGIN_INIT
Relative jump to Init() function.
2.1.6 MXP_PLUGIN_SET
Relative jump to Set() function.
2.1.7 MXP_PLUGIN_UNSET
Relative jump to Unset() function.
2.1.8 MXP_PLUGIN_DEINIT
Relative jump to Deinit() function.
2.1.9 MXP_PLUGIN_MUSIC_FWD
Relative jump to Forward() function.
2.1.10 MXP_PLUGIN_MUSIC_RWD
Relative jump to Rewind() function.
2.1.11 MXP_PLUGIN_MUSIC_PAUSE
Relative jump to Pause() function.
2.1.12 MXP_PLUGIN_INFO
Pointer to the 'Info' structure.
2.1.13 MXP_PLUGIN_EXTENSIONS
Pointer to the list of 'Extensions' structures.
2.1.14 MXP_PLUGIN_SETTINGS
Pointer to the list of 'Settings' structures.
2.2 Info Structure
Defined as 'mxp_struct_info'. Each entry is 4 bytes long. The number of structures is limited to one. Best approach is to use it for example as:
lea     ogg_header,a0
movea.l (a0,MXP_PLUGIN_INFO),a0
move.l  (a0,mxp_struct_info_plugin_author),d0
:
:
2.2.1 mxp_struct_info_plugin_author
Pointer to NULL terminated string with plugin author's name.
2.2.2 mxp_struct_info_plugin_version
Pointer to NULL terminated string with plugin version.
2.2.3 mxp_struct_info_replay_name
Pointer to NULL terminated string with replay routine's name.
2.2.4 mxp_struct_info_replay_author
Pointer to NULL terminated string with replay routine's author name.
2.2.5 mxp_struct_info_replay_version
Pointer to NULL terminated string with replay routine's version.
2.2.6 mxp_struct_info_flags
This bitfield tells plugin which resources wish to use. Currently defined are:
MXP_FLG_USE_DSP: plugin uses DSP
MXP_FLG_USE_DMA: plugin uses DMA sound system
MXP_FLG_USE_020: plugin uses 020+ CPU
MXP_FLG_USE_FPU: plugin uses FPU
So you use for example:
dc.l   MXP_FLG_USE_DSP|MXP_FLG_USE_DMA|MXP_FLG_USE_020
2.3 Settings Structure
Defined as 'mxp_struct_settings'. Each entry is 4 bytes long. The number of structures is unlimited.
2.3.1 mxp_struct_settings_name
Pointer to NULL terminated string with parameter's name. If NULL, no more parameters are read. This name is used as unique identifier so you can't use two parameters with the same name.
2.3.2 mxp_struct_settings_type
Type of parameter. Currently defined are:
MXP_PAR_TYPE_BOOL: handle as 1 / 0 switch.
MXP_PAR_TYPE_CHAR: handle as string parameter
MXP_PAR_TYPE_INT: handle as signed integer parameter
These types tell player what value will get after calling 'get' function or to what type should convert value which is on input of 'set' function.
Please, don't use very long names for these parametes, you wont break anything, but you can get to the situation when you will have 'very_long_parameter_name1' and 'very_long_parameter_name2' and application will see one 'very_long_parame' parameter).
There's a little different behaviour by interpreting values of these parameters according to the group they belong to.
For plugin parametes applies one general rule: output size is limited by the size of Plugin Info dialog. It's simply because there's a very small chance you will ever need more than a few charactes (in fact I can't imagine what kind of parameter you could pass to the plugin as CHAR type). Except this, there's a different interpretation of the BOOL parameter (comparing to module parameters): it's shown as checkable custom defined colour icon. So state '1' means icon checked and vice versa.
However, module parameters give the user possibility to scroll these values. So your parameter value (e.g. songname) can contain unlimited number of characters. Around this value will be shown left/right arrows which allow user to view te rest of text. Please note EVERY parameter will be converted to the string - both BOOL type (as 'Yes' / 'No') and integer.
Each parameter type could/should be ORed with one or more flags:
MXP_FLG_INFOLINE: parameter + its value will be shown on panel in scrolling infoline
MXP_FLG_MOD_PARAM: parameter + its value will be shown in Module Info window
MXP_FLG_PLG_PARAM: parameter + its value will be shown in Plugin Info window
For example:
  dc.l   param_original
  dc.l   MXP_PAR_TYPE_BOOL|MXP_FLG_MOD_PARAM
  :
  :
param_original:
  dc.b   'Original',0
and when 'get' will return 0, in the Module Info window you will see 'Original: No'.
2.3.3 mxp_struct_settings_routine_set
Pointer to the 'Set' function. As was mentioned, if NULL, parameter will be marked as 'read only' (will be shaded in info window). Of course, this has some sense only in plugin parameters.
Please, don't mess up this function with the one discussed in chapter 3.
Typical use of this function is to switch on/off some parameter (interpolation, surround, ...) or to pass some integer value (e.g. playtime if plugin doesn't provide accurate playtime determination). However, there's also text parameter for some special use.
After calling this function from application, plugin should expect value at MXP_PLUGIN_PARAMETER place. Application doesn't check any return code.
2.3.4 mxp_struct_settings_routine_get
Pointer to 'Get' function. This pointer can never be NULL! Plugin should return wanted value at MXP_PLUGIN_PARAMETER place. Application doesn't check any return code.
2.4 Extensions Structure
Defined as 'mxp_struct_extensions'. Each entry is 4 bytes long. The number of structures is unlimited.
2.4.1 mxp_struct_extensions_string
Pointer to NULL terminated string with supported module file extension. If NULL, no more parameters are read.
2.4.2 mxp_struct_extensions_name
Pointer to NULL terminated string with the module extension full name.
For example:
  dc.l   am_extension_string
  dc.l   am_extension_name
  :
  :
am_extension_string:
  dc.b   "AM",0
am_extension_name:
  dc.b   "ACE Module",0
This is the base interface between application and plugin. All functions except Playtime() should return result status in d0 register - MXP_OK or MXP_ERROR.
3.1 Register()
Register module. On the input (in the input buffer) plugin should expect pointer to two another pointers:
buffer with module binary
length of this buffer
Correct handling of this input is as follows:
  movea.l  ogg_header+MXP_PLUGIN_PARAMETER,a0
  move.l   (a0)+,ogg_buffer
  move.l   (a0),ogg_buffer_length
Please note you have to fill all things in Settings structure *NOW* !
Return MXP_OK if plugin is able to replay given module. Return MXP_ERROR if not. This could occur when you compare file header to some expected value and the values don't match.
3.2 Playtime()
Return playtime for given module - some plugins are able to calculate it. If your plugin isn't able to do it, return let's say 2 minutes and allow user to change this value.
3.3 Init()
Initialize plugin. Allocate space, precalc tables etc. Don't do anything with HW registers!
3.4 Set()
Set module, i.e. save HW registers, set new ones, activate playing.
3.5 Unset()
Opaque to Set(), i.e. stop playing, restore HW registers.
3.6 Forward()
Skip some time forward. Currently unimplemented.
3.7 Rewind()
Move back some time. Currently unimplemented.
3.8 Pause()
Pause playing. You have to care about on/off state, application will just call this function. Don't forget to deactivate pause status when Unset() is called.
Some important remarks about plugin making.
4.1 Startup Process
Load plugin, call Init().
When user loads module, call Register(). [after this step infoline will be filled via Get() functions in Settings structure]
If Register() was OK, call Set(); if not, release module from memory (!)
When user stops playing, call Unset().
When user presses pause key, call Pause().
[Currently unimplemented] When user request to remove plugin, call Deinit().
4.2 Communication Issues
The application and plugin behave as classical client/server model. Each plugin is handled as new process (using Pexec() call). That means they are independent processes which theoretically can't share any memory. There are some solutions how to do it, I chose probably the simplest one - they share some global memory. This applies to two buffers:
module buffer, which has to be accessible by both plugin and application
communication buffer, where plugin and application exchange data
Since plugin has no reason to access any of application's data, there's no problem to set memory model as 'Private' for the application. However, application needs some access to plugin's structures (parameters) so *YOU HAVE TO SET AT LEAST READABLE ACCESS TO THE PLUGIN*. This can by done by many tools, from CPX to Jinnee's info dialog.
4.3 Stability
In the ideal case when application is free of bugs there's still very important thing which applies to overall application stability: stability of plugins.
Typical example, how to hang FreeMiNT kernel and/or XaAES is to use some custom exception vector, e.g. Timer A/B/C/D. If there is no way how to avoid using it, please (PLEASE!) use following approach:
always save/restore everything you use
try to call original exception vector after you finished your stuff (yes, there are some potential problems with the situation if some other application uses similar approach and then such application restore original exception pointer... this is trying to solve XBRA protocol but... you know ;)
set memory model to 'Super'. This means except your plugin you provide access to your application space also for all applications in Supervisor mode (i.e. FreeMiNT kernel). This fix well know FATAL ERRORs in FreeMiNT with memory protection.
I have to say, it's not ideal solution but there's no other way. MiNT guys are very upset when comes a talk to similar practices but do we want some good player or not?
4.4 What You Don't Need To Do
Application try to do some work for you, concretly:
lock/unlock resources you've specified in the info structure. So you don't need to do it by hand. Or... you MUST NOT to do it ;)
convert bools and ints to strings and vice versa. So if you want to show your plugin replays 8 channel module you don't need to convert the number '8' to the string.
Mshrink(). Avoid using of this call, this is called on the startup time.
Super() calls. You don't need to call this call anytime. Every function as called via Supexec(). Yeah yeah, I know, not very safe but I have the control over super/user stack which I found as the most important factor.
Since I'm the author of this API it comes to me as very easy and understable. However, don't hesistate to contact me with ANY your question, I'm open to answer even the most primitive one you can imagine ;) The main goal is to motivate people to code as much plugins as possible. So don't wait, take your favorite replay routine and make new MXP plugin!
You can reach me 7 days of week at:
mikro at hysteria dot sk
In case of some unexpected things (server crash etc) you can contact Xi, the author of the most of plugins in this release:
xi at napri dot sk... happy plugging!