aboutsummaryrefslogtreecommitdiffhomepage
#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