aboutsummaryrefslogtreecommitdiff
path: root/doc/APIdoc.md
blob: 8e3a18190935a48cef05ccac8b3fd873402762b0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# 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.

Aside from the generic functionalities that you can add by implementing the class `qmpFuncBaseIntf`,
You can add these features to QMidiPlayer by implementing their corresponding classes:

- MIDI File Reader (`qmpFileReader`)
- MIDI Out Device (`qmpMidiOutDevice`)

...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`)
- File read finished, when the entire file has been read. (via `(un)registerFileReadFinishedHandlerIntf`)
- UIHooks, which are triggered by UI events. (via `(un)registerUIHook`)  
  The first argument defines which event shall trigger the callback. All valid events are listed below:
      * `main.stop`: stop button clicked, or playback stopped because end of track reached, or (oddly) switching tracks.
      * `main.start`: track started playing (includes resuming from pause and switching tracks).
      * `main.pause`: playback paused
      * `main.reset`: (odd enough) when loading a new file

Hooks use the universal `ICallBack` interface. Some of those also accept a pointer to a function (`callback_t`).

When you register a hook, you provide the core with a instance of your class that implements the `ICallBack` 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`. All other callbacks at this time have `nullptr`
as their `callerdata`. **Don't try directly modifying the members of callerdata!**

# 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 generic 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. Functionalities 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 implement your own visualization
as a normal functionality. The code of the default visualization plugin may offer some additional help.

## 4.3 MIDI File Reader
This is not strictly a "functionality", because its interface `qmpFileReader` 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 to the built-in MIDI mapper.

# 5. Interacting with the core

Your plugin gets a pointer to an object of the class `qmpPluginAPI` after it is initialized, which is what you
should use to interact with the core.

This interface provides a wide range of APIs that...
- provide information on the file being played
- get/change the current state of the player
- modify events when a midi file is being read
- register custom interfaces (functionalities, output devices, file readers, handlers and hooks)
- provide options management (`<register|get|set>Option*`)


# 6. General Considerations & Notes

1. If you implemented a API that returns a pointer to an instance of some nonabstract class, 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
`qmpFileReader::readFile` which returns a pointer of the `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.
4. The core does not handle MIDI running status, nor does it send events to any component with running status either.
That is to say, the `type` parameter found in `SEvent` and `SEventCallBackData` should always be greater than or equal to
0x80.

# 7. Reference

## 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`  
_absolute_ 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. Note that pitch wheel events use both parameters (p2 for MSB).
- `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=nullptr)`  
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.
The order of events in `eventList` does not matter as long as the timestamps are correct.

- `std::vector<SEvent> 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.
** Check the validity of the index first! This operator doesn't do the check. **

### class `CMidiFile`
Describes a MIDI file. A MIDI file consists of multiple MIDI tracks.

- `bool valid`  
Is the MIDI file a valid one? If your file reader encountered an unrecognised
file, set it to false.
- `char* title`  
Title of the MIDI file.
- `char* copyright`  
Copyright information of the MIDI file.
- `std::vector<CMidiTrack> 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 file.

### class `ICallBack`
Generic callback function that can be used for hooking the core.

- `ICallBack()`  
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 ~ICallBack()`  
Virtual empty destructor.

### class `qmpFileReader`
MIDI file reader interface. Use this to implement your file importer.

- `qmpFileReader()`  
Default empty constructor.
- `virtual ~qmpFileReader()`  
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. Don't handle the
destruction of the resulting structure as the core will do it.  
Read section 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, only the current event is 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.

### class `qmpFuncBaseIntf`
This is the base class for all QMP functionalities.

- `qmpFuncBaseIntf()`  
Default empty constructor.
- `virtual ~qmpFuncBaseIntf()`  
Virtual empty destructor.
- `virtual void show()=0`  
Called by the core when the functionality is requested to show up.
- `virtual void close()=0`
Called by the core when the functionality is requested to be closed.
Never called for a functionality whose `checkable` attribute is registered as `false`.

### class `qmpMidiOutDevice`
Interface for a midi output device.

- `qmpMidiOutDevice()`  
Default empty constructor.
- `virtual ~qmpMidiOutDevice()`  
Virtual empty destructor.
- `virtual void deviceInit()=0`  
Initializes the device.
- `virtual void deviceDeinit()=0`  
Deinitializes the device.
- `virtual void basicMessage(uint8_t type,uint8_t p1,uint8_t p2)=0`  
Sends a basic midi message.  
Note that the core doesn't make use of running status.
- `virtual void extendedMessage(uint8_t length,const char* data)=0`  
Sends an extended message. ("system exclusive message").
- `virtual void rpnMessage(uint8_t ch,uint16_t type,uint16_t val)=0`  
Sends a registered parameter number controller message.
- `virtual void nrpnMessage(uint8_t ch,uint16_t type,uint16_t val)=0`  
Sends a non-registered parameter number controller message.
- `virtual void panic(uint8_t ch)=0`  
Stops all voices on a channel.
- `virtual void reset(uint8_t ch)=0`  
Reset the status of a channel.
- `virtual void onMapped(uint8_t ch,int refcnt)=0`  
Called when the device is mapped. `ch` is the channel it is mapped to.
`refcnt` is the number of channels mapped to this device after the remapping.
- `virtual void onUnmapped(uint8_t ch,int refcnt)=0`  
Called when the device is mapped. Parameters are the sams as `onMapped()`.

### class `qmpPluginAPI`
Main way to interact with the core.

Yet to be documented.