aboutsummaryrefslogtreecommitdiffhomepage
path: root/midimonster.h
blob: 15ce34a34a379f4200deea5349886d57f6b233ae (plain)
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
291
292
293
294
295
296
297
#ifndef MIDIMONSTER_HEADER
#define MIDIMONSTER_HEADER
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>

/* Core version unless set by the build process */
#ifndef MIDIMONSTER_VERSION
	#define MIDIMONSTER_VERSION "v0.7-dist"
#endif

/* Set backend name if unset */
#ifndef BACKEND_NAME
	#define BACKEND_NAME "unspec"
#endif

/* API call attributes and visibilities */
#ifndef MM_API
	#ifdef _WIN32
		#define MM_API __attribute__((dllimport))
	#else
		#define MM_API
	#endif
#endif

/* Some build systems may apply the -fvisibility=hidden parameter from the core build to the backends, so mark the init function visible */
#ifndef MM_PLUGIN_API
	#ifdef _WIN32
		#define MM_PLUGIN_API __attribute__((dllexport))
	#else
		#define MM_PLUGIN_API __attribute__((visibility ("default")))
	#endif
#endif

/* Straight-forward min / max macros */
#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))

/* Clamp a value to a range */
#define clamp(val,max,min) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))

/* Log function prototype - do not use directly. Use the LOG/LOGPF/DBGPF macros below instead */
MM_API __attribute__((format(printf, 3, 4))) int log_printf(int level, char* module, char* fmt, ...);

/* Debug messages only compile in when DEBUG is set */
#ifdef DEBUG
	#define DBGPF(format, ...) log_printf(1, (BACKEND_NAME), format "\n", __VA_ARGS__)
#else
	#define DBGPF(format, ...)
#endif

/* Log messages should be routed through these macros to ensure interoperability with different core implementations */
#define LOGPF(format, ...) log_printf(0, (BACKEND_NAME), format "\n", __VA_ARGS__)
#define LOG(message) log_printf(0, (BACKEND_NAME), message "\n")

/* Stop compilation if the build system reports an error */
#ifdef BUILD_ERROR
	#error The build system reported an error, compilation stopped. Refer to the invocation for this compilation unit for more information.
#endif

/* Pull in additional defines for non-linux platforms */
#include "portability.h"

/* Default configuration file name to read when no other is specified */
#ifndef DEFAULT_CFG
	#define DEFAULT_CFG "monster.cfg"
#endif

/* Default backend plugin location */
#ifndef PLUGINS
	#ifndef _WIN32
		#define PLUGINS "./backends/"
	#else
		#define PLUGINS "backends\\"
	#endif
#endif

/* Forward declare some of the structs so we can use them in each other */
struct _channel_value;
struct _backend_channel;
struct _backend_instance;
struct _managed_fd;

/*
 * Backend module callback defines
 *
 * The lifecycle of a backend module is as follows
 * 	* int init()
 * 		The only function that should be exported by the shared object.
 * 		Called when the shared object is attached. Should register
 * 		a backend structure containing callable entry points with the core.
 * 		Returning anything other than zero causes midimonster to fail the
 * 		startup checks.
 * 	* mmbackend_configure
 * 		Parse backend-global configuration options from the user-supplied
 * 		configuration file. Returning a non-zero value fails config parsing.
 * 	* mmbackend_instance
 * 		Allocate the backend-specific data parts of the supplied instance
 * 		structure. Returning non-zero signals an error condition and
 * 		terminates the program.
 * 	* mmbackend_configure_instance
 * 		Parse instance configuration from the user-supplied configuration
 * 		file. Returning a non-zero value fails config parsing.
 * 	* mmbackend_channel
 * 		Parse a channel-spec to be mapped to/from. The `flags` parameter supplies
 * 		additional information to the parser, such as whether the channel is being
 * 		queried for use as input (to the MIDIMonster core) and/or output
 * 		(from the MIDIMonster core) channel (on a per-query basis).
 * 		Returning NULL signals an out-of-memory condition and terminates the program.
 * 	* mmbackend_start
 * 		Called after all instances have been created and all mappings
 * 		have been set up. Only backends for which instances have been configured
 * 		receive the start call. May be used to connect to backing hardware
 * 		or to update runtime-specific data in the various data structures.
 * 		Returning a non-zero value signals an error starting the backend
 * 		and stops further progress.
 * 	* Normal processing loop starts here
 * 		* mmbackend_process_fd
 * 			Handle data from signaled fds registered via mm_manage_fd.
 * 			Push generated events to the core with mm_channel_event.
 * 			All registered fds that are ready to read are pushed at once.
 * 			Backends that have not registered any fds are still called with
 * 			nfds set to 0 in order to support polling backends.
 * 			Returning a non-zero value signals an error and gracefully terminates
 * 			the program.
 *		* mmbackend_handle_event
 *			An event resulted in a channel for an instance being set.
 *			Called once per changed instance with all updated channels for that
 *			specific instance.
 *			Returning a non-zero value terminates the program.
 *		* (optional) mmbackend_interval
 *			Return the maximum sleep interval for this backend in milliseconds.
 *			If not implemented, a maximum interval of one second is used.
 *			Returning 0 signals that the backend does not have a minimum
 *			interval.
 *	* mmbackend_shutdown
 *		Clean up all allocations, finalize all hardware connections. All registered
 *		backends receive the shutdown call, regardless of whether they have been
 *		started previously.
 *		Return value is currently ignored.
 */
typedef int (*mmbackend_handle_event)(struct _backend_instance* inst, size_t channels, struct _backend_channel** c, struct _channel_value* v);
typedef int (*mmbackend_create_instance)(struct _backend_instance* inst);
typedef struct _backend_channel* (*mmbackend_parse_channel)(struct _backend_instance* instance, char* spec, uint8_t flags);
typedef void (*mmbackend_free_channel)(struct _backend_channel* c);
typedef int (*mmbackend_configure)(char* option, char* value);
typedef int (*mmbackend_configure_instance)(struct _backend_instance* instance, char* option, char* value);
typedef int (*mmbackend_process_fd)(size_t nfds, struct _managed_fd* fds);
typedef int (*mmbackend_start)(size_t ninstances, struct _backend_instance** inst);
typedef uint32_t (*mmbackend_interval)();
typedef int (*mmbackend_shutdown)(size_t ninstances, struct _backend_instance** inst);

/* Bit masks for the `flags` parameter to mmbackend_parse_channel */
typedef enum {
	mmchannel_input = 0x1,
	mmchannel_output = 0x2
} mmbe_channel_flags;

/* Channel event value, .normalised is used by backends to determine channel values */
typedef struct _channel_value {
	union {
		double dbl;
		uint64_t u64;
	} raw;
	double normalised;
} channel_value;

/* 
 * Backend callback structure
 * Used to register a backend with the core using mm_backend_register()
 */
typedef struct /*_mm_backend*/ {
	char* name;
	mmbackend_configure conf;
	mmbackend_create_instance create;
	mmbackend_configure_instance conf_instance;
	mmbackend_parse_channel channel;
	mmbackend_handle_event handle;
	mmbackend_process_fd process;
	mmbackend_start start;
	mmbackend_shutdown shutdown;
	mmbackend_free_channel channel_free;
	mmbackend_interval interval;
} backend;

/* 
 * Backend instance structure - do not allocate directly!
 * Use the memory returned by mm_instance()
 */
typedef struct _backend_instance {
	backend* backend;
	uint64_t ident;
	void* impl;
	char* name;
} instance;

/* 
 * Instance channel structure
 * Backends may either manage their own channel registry or use the global
 * channel store via the mm_channel() API
 */
typedef struct _backend_channel {
	instance* instance;
	uint64_t ident;
	void* impl;
} channel;

/*
 * File descriptor structure passed for backend handling
 * Register for the core event loop using mm_manage_fd()
 */
typedef struct _managed_fd {
	int fd;
	backend* backend;
	void* impl;
} managed_fd;

/*
 * Register a new backend.
 */
MM_API int mm_backend_register(backend b);

/*
 * Finds an instance matching the specified backend and identifier.
 * Since setting an identifier for an instance is optional, this may not work
 * depending on the backend. Instance identifiers may for example be set in the
 * backends mmbackend_start call.
 */
MM_API instance* mm_instance_find(char* backend, uint64_t ident);

/*
 * This function is the main interface to the core-provided channel registry.
 * This API is just a convenience function. Creating and managing a
 * backend-internal channel store is possible (and encouraged for performance
 * reasons).
 *
 * Channels are identified by the (instance, ident) tuple within the registry.
 *
 * This API provides a pointer to a channel structure, pre-filled with the
 * provided instance reference and identifier.
 * The `create` parameter is a boolean flag indicating whether a channel
 * matching the `ident` parameter should be created in the global channel store
 * if none exists yet. If the instance already registered a channel matching
 * `ident`, a pointer to the existing channel is returned.
 * 
 * When returning pointers from a backend-local channel store, the
 * returned pointers must stay valid over the lifetime of the instance and
 * provide valid `instance` members, as they are used for callbacks.
 * For each channel with a non-NULL `impl` field registered using
 * this function, the backend will receive a call to its channel_free
 * function (if it exists).
 */
MM_API channel* mm_channel(instance* i, uint64_t ident, uint8_t create);

/*
 * When using the core-provided channel registry, the identification
 * member of the structure must only be updated using this API.
 * The tuple of (instance, ident) is used as key to the backing
 * storage of the channel registry, thus the registry must be notified
 * of changes.
 */
MM_API void mm_channel_update(channel* c, uint64_t ident);

/*
 * Register (manage = 1) or unregister (manage = 0) a file descriptor to be
 * selected on. The backend will be notified when the descriptor becomes ready
 * to read via its registered mmbackend_process_fd call. The `impl` argument
 * will be provided within the corresponding managed_fd structure upon callback.
 */
MM_API int mm_manage_fd(int fd, char* backend, int manage, void* impl);

/*
 * Notifies the core of a channel event. Called by backends to inject events
 * gathered from their backing implementation.
 */
MM_API int mm_channel_event(channel* c, channel_value v);

/*
 * Query all active instances for a given backend.
 * *i will need to be freed by the caller.
 */
MM_API int mm_backend_instances(char* backend, size_t* n, instance*** i);

/*
 * Query an internal timestamp, which is updated every core iteration.
 * This timestamp should not be used as a performance counter, but can be used
 * for timeouting. Resolution is milliseconds.
 */
MM_API uint64_t mm_timestamp();

/*
 * Create a channel-to-channel mapping. This API should not be used by backends.
 * It is only exported for core modules.
 */
int mm_map_channel(channel* from, channel* to);
#endif