summaryrefslogtreecommitdiff
path: root/plugins/uade2/plugin.c
diff options
context:
space:
mode:
authorGravatar waker <wakeroid@gmail.com>2011-03-23 21:26:26 +0100
committerGravatar waker <wakeroid@gmail.com>2011-03-23 21:26:26 +0100
commitfd302d7abc36942e7ff14b22fae1e72b4495bef1 (patch)
tree2a36f8361c907a5bea91a9d905957a709f31ea64 /plugins/uade2/plugin.c
parent11e63b53b8c91da89592c373bb32fc2b656c6024 (diff)
renamed soundtouch and uade2 plugin folders and output .so
Diffstat (limited to 'plugins/uade2/plugin.c')
-rw-r--r--plugins/uade2/plugin.c692
1 files changed, 692 insertions, 0 deletions
diff --git a/plugins/uade2/plugin.c b/plugins/uade2/plugin.c
new file mode 100644
index 00000000..a07f82fe
--- /dev/null
+++ b/plugins/uade2/plugin.c
@@ -0,0 +1,692 @@
+/*
+ ddb_input_uade2 - UADE input plugin for DeaDBeeF player
+ Copyright (C) 2009-2011 Alexey Yakovenko <waker@users.sourceforge.net>
+ based on UADE2 plugin for Audacious, Copyright (C) 2005-2006 Heikki Orsila, UADE TEAM
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <signal.h>
+#include "../../deadbeef.h"
+#include "uadeipc.h"
+#include "eagleplayer.h"
+#include "uadeconfig.h"
+#include "uadecontrol.h"
+#include "uadeconstants.h"
+#include "ossupport.h"
+#include "uadeconf.h"
+#include "effects.h"
+#include "sysincludes.h"
+#include "songdb.h"
+#include "songinfo.h"
+
+#define min(x,y) ((x)<(y)?(x):(y))
+#define max(x,y) ((x)>(y)?(x):(y))
+
+#define trace(...) { fprintf(stderr, __VA_ARGS__); }
+//#define trace(fmt,...)
+
+static char configname[PATH_MAX];
+static struct uade_config config_backup;
+static time_t config_load_time;
+static time_t md5_load_time;
+static char md5name[PATH_MAX];
+static char songconfname[PATH_MAX];
+static char gui_filename[PATH_MAX];
+
+static const char *
+get_uade_base_conf_dir (void) {
+ return UADE_CONFIG_BASE_DIR;
+}
+
+static void load_content_db(void)
+{
+ struct stat st;
+ time_t curtime = time(NULL);
+ char name[PATH_MAX];
+
+ if (curtime)
+ md5_load_time = curtime;
+
+ if (md5name[0] == 0) {
+ char *home = uade_open_create_home();
+ if (home)
+ snprintf(md5name, sizeof md5name, "%s/.uade2/contentdb", home);
+ }
+
+ /* User database has priority over global database, so we read it first */
+ if (md5name[0]) {
+ if (stat(md5name, &st) == 0) {
+ if (uade_read_content_db(md5name))
+ return;
+ } else {
+ FILE *f = fopen(md5name, "w");
+ if (f)
+ fclose(f);
+ uade_read_content_db(md5name);
+ }
+ }
+
+ snprintf(name, sizeof name, "%s/contentdb.conf", get_uade_base_conf_dir ());
+ if (stat(name, &st) == 0)
+ uade_read_content_db(name);
+}
+
+/* xmms initializes uade by calling this function */
+static void uade_init(void)
+{
+ static int initialized = 0;
+ if (!initialized) {
+ char *home;
+ int config_loaded;
+
+ config_load_time = time(NULL);
+
+ config_loaded = uade_load_initial_config(configname, sizeof configname,
+ &config_backup, NULL);
+
+ load_content_db();
+
+ uade_load_initial_song_conf(songconfname, sizeof songconfname,
+ &config_backup, NULL);
+
+ home = uade_open_create_home();
+
+ if (home != NULL) {
+ /* If config exists in home, ignore global uade.conf. */
+ snprintf(configname, sizeof configname, "%s/.uade2/uade.conf", home);
+ }
+
+ if (config_loaded == 0) {
+ fprintf(stderr, "No config file found for UADE XMMS plugin. Will try to load config from\n");
+ fprintf(stderr, "$HOME/.uade2/uade.conf in the future.\n");
+ }
+ initialized = 1;
+ }
+}
+
+static void uade_get_song_info(const char *filename, char **title, int *length)
+{
+ char tempname[PATH_MAX];
+ const char *t;
+
+ if (strncmp(filename, "uade://", 7) == 0)
+ filename += 7;
+
+ strlcpy(tempname, filename, sizeof tempname);
+ t = basename(tempname);
+ if (t == NULL)
+ t = filename;
+ if ((*title = strdup(t)) == NULL)
+ trace("Not enough memory for song info.\n");
+ *length = -1;
+}
+
+int uade_get_max_subsong(struct uade_state *state, int def)
+{
+ int subsong;
+ subsong = -1;
+ if (state->song != NULL)
+ subsong = state->song->max_subsong;
+ if (subsong == -1)
+ subsong = def;
+ return subsong;
+}
+
+
+int uade_get_min_subsong(struct uade_state *state, int def)
+{
+ int subsong;
+ subsong = -1;
+ if (state->song != NULL)
+ subsong = state->song->min_subsong;
+ if (subsong == -1)
+ subsong = def;
+ return subsong;
+}
+
+DB_functions_t *deadbeef;
+static DB_decoder_t plugin;
+
+static const char *exts[] = { NULL };
+static const char *prefixes[] = { "mod", NULL };
+static const char *filetypes[] = { "UADE", NULL };
+
+#define UADE_BUFFER_SIZE 100000
+
+typedef struct {
+ DB_fileinfo_t info;
+ struct uade_state state;
+ int controlstate;
+ int record_playtime;
+ int remaining;
+ char buffer[UADE_BUFFER_SIZE];
+ int subsong_end;
+ int song_end_trigger;
+ int64_t skip_bytes;
+ int abort_playing;
+ int uade_seek_forward;
+} uade_info_t;
+
+DB_fileinfo_t *
+uadeplug_open (uint32_t hints) {
+ DB_fileinfo_t *_info = malloc (sizeof (uade_info_t));
+ uade_info_t *info = (uade_info_t *)_info;
+ memset (info, 0, sizeof (uade_info_t));
+ return _info;
+}
+
+static int
+uadeplug_init (DB_fileinfo_t *_info, DB_playItem_t *it) {
+ uade_info_t *info = (uade_info_t *)_info;
+
+ uade_init ();
+ char modulename[PATH_MAX];
+ char playername[PATH_MAX];
+ char scorename[PATH_MAX];
+ char gui_module_filename[PATH_MAX];
+ char gui_player_filename[PATH_MAX];
+
+ info->state.config = config_backup;
+ info->state.validconfig = 1;
+
+ printf ("probing the file\n");
+ int ret = uade_is_our_file(it->fname, 0, &info->state);
+
+ if (!ret) {
+ trace ("not uade file\n");
+ return -1;
+ }
+ strlcpy(modulename, it->fname, sizeof modulename);
+ trace ("modulename: %s\n", modulename);
+ strlcpy(gui_module_filename, it->fname, sizeof gui_module_filename);
+ trace ("gui_module_fname: %s\n", gui_module_filename);
+
+ snprintf(scorename, sizeof scorename, "%s/score", get_uade_base_conf_dir ());
+ trace ("scorename: %s\n", scorename);
+
+ if (strcmp(info->state.ep->playername, "custom") == 0) {
+ strlcpy(playername, modulename, sizeof playername);
+ modulename[0] = 0;
+ gui_module_filename[0] = 0;
+ } else {
+ snprintf(playername, sizeof playername, "%s/players/%s", get_uade_base_conf_dir (), info->state.ep->playername);
+ }
+ trace ("playername: %s\n", playername);
+
+ if (!uade_alloc_song(&info->state, it->fname)) {
+ trace ("uade_alloc_song fail\n");
+ return -1;
+ }
+
+ uade_set_ep_attributes(&info->state);
+
+ uade_set_song_attributes(&info->state, playername, sizeof playername);
+
+ uade_set_effects(&info->state);
+
+ strlcpy(gui_player_filename, playername, sizeof gui_player_filename);
+
+ if (!info->state.pid) {
+ char configname[PATH_MAX];
+ snprintf(configname, sizeof configname, "%s/uaerc", UADE_CONFIG_BASE_DIR);
+ uade_spawn(&info->state, UADE_CONFIG_UADE_CORE, configname);
+ }
+
+ printf ("uade_song_initialization\n");
+ ret = uade_song_initialization(scorename, playername, modulename, &info->state);
+ if (ret) {
+ if (ret != UADECORE_CANT_PLAY && ret != UADECORE_INIT_ERROR) {
+ fprintf(stderr, "Can not initialize song. Unknown error.\n");
+ return -1;
+ }
+ uade_unalloc_song(&info->state);
+ return -1;
+ }
+ printf ("init done\n");
+ int minsong = uade_get_min_subsong (&info->state, 0);
+ int maxsong = uade_get_max_subsong (&info->state, 0);
+ info->state.song->cur_subsong = it->tracknum;
+ uade_change_subsong(&info->state);
+
+ _info->fmt.bps = 16;
+ _info->fmt.channels = UADE_CHANNELS;
+ _info->fmt.samplerate = info->state.config.frequency;
+ for (int i = 0; i < _info->fmt.channels; i++) {
+ _info->fmt.channelmask |= 1 << i;
+ }
+ _info->readpos = 0;
+ _info->plugin = &plugin;
+ info->record_playtime = 1;
+ info->controlstate = UADE_S_STATE;
+
+ return 0;
+}
+
+// free everything allocated in _init
+static void
+uadeplug_free (DB_fileinfo_t *_info) {
+ uade_info_t *info = (uade_info_t *)_info;
+ if (info) {
+ uade_unalloc_song(&info->state);
+ if (info->state.pid) {
+ kill(info->state.pid, SIGTERM);
+ }
+ free (info);
+ }
+}
+
+int
+uadeplug_frame (uade_info_t *info) {
+ uint8_t space[UADE_MAX_MESSAGE_SIZE];
+ struct uade_msg *um = (struct uade_msg *) space;
+ uint16_t *sm;
+ int i;
+ unsigned int play_bytes, tailbytes = 0;
+ uint64_t subsong_bytes = 0;
+ int framesize = UADE_CHANNELS * UADE_BYTES_PER_SAMPLE;
+ int left = 0;
+ char gui_formatname[256];
+ char gui_modulename[256];
+ char gui_playername[256];
+ char *reason;
+ uint32_t *u32ptr;
+ int frame_received = 0;
+
+ while (!frame_received) {
+ if (info->controlstate == UADE_S_STATE) {
+
+ assert(left == 0);
+
+ if (info->abort_playing) {
+ info->record_playtime = 0;
+ break;
+ }
+
+ if (info->uade_seek_forward) {
+ info->skip_bytes += info->uade_seek_forward * (UADE_BYTES_PER_FRAME * info->state.config.frequency);
+ info->uade_seek_forward = 0;
+ }
+
+ left = uade_read_request(&info->state.ipc);
+
+ if (uade_send_short_message(UADE_COMMAND_TOKEN, &info->state.ipc)) {
+ fprintf(stderr, "Can not send token.\n");
+ return -1;
+ }
+ info->controlstate = UADE_R_STATE;
+
+ } else {
+
+ if (uade_receive_message(um, sizeof(space), &info->state.ipc) <= 0) {
+ fprintf(stderr, "Can not receive events from uade\n");
+ exit(-1);
+ }
+
+
+ switch (um->msgtype) {
+
+ case UADE_COMMAND_TOKEN:
+ info->controlstate = UADE_S_STATE;
+ break;
+
+ case UADE_REPLY_DATA:
+ sm = (uint16_t *) um->data;
+ for (i = 0; i < um->size; i += 2) {
+ *sm = ntohs(*sm);
+ sm++;
+ }
+
+ if (info->subsong_end) {
+ play_bytes = tailbytes;
+ tailbytes = 0;
+ } else {
+ play_bytes = um->size;
+ }
+
+ if (info->subsong_end == 0 && info->song_end_trigger == 0 &&
+ uade_test_silence(um->data, play_bytes, &info->state)) {
+ info->subsong_end = 1;
+ }
+
+ subsong_bytes += play_bytes;
+ info->state.song->out_bytes += play_bytes;
+
+ if (info->skip_bytes > 0) {
+ if (play_bytes <= info->skip_bytes) {
+ info->skip_bytes -= play_bytes;
+ play_bytes = 0;
+ } else {
+ play_bytes -= info->skip_bytes;
+ info->skip_bytes = 0;
+ }
+ }
+
+ uade_effect_run(&info->state.effects, (int16_t *) um->data, play_bytes / framesize);
+
+ // ... copy data ...
+ memcpy (info->buffer + info->remaining, um->data, play_bytes);
+ frame_received = 1;
+ info->remaining += play_bytes;
+
+ if (info->state.config.timeout != -1 && info->state.config.use_timeouts) {
+ if (info->song_end_trigger == 0) {
+ if (info->state.song->out_bytes / (UADE_BYTES_PER_FRAME * info->state.config.frequency) >= info->state.config.timeout) {
+ info->song_end_trigger = 1;
+ info->record_playtime = 0;
+ }
+ }
+ }
+
+ if (info->state.config.subsong_timeout != -1 && info->state.config.use_timeouts) {
+ if (info->subsong_end == 0 && info->song_end_trigger == 0) {
+ if (subsong_bytes / (UADE_BYTES_PER_FRAME * info->state.config.frequency) >= info->state.config.subsong_timeout) {
+ info->subsong_end = 1;
+ info->record_playtime = 0;
+ }
+ }
+ }
+
+ assert (left >= um->size);
+ left -= um->size;
+ break;
+
+ case UADE_REPLY_FORMATNAME:
+ uade_check_fix_string(um, 128);
+ strlcpy(gui_formatname, (char *) um->data, sizeof gui_formatname);
+ strlcpy(info->state.song->formatname, (char *) um->data, sizeof info->state.song->formatname);
+ break;
+
+ case UADE_REPLY_MODULENAME:
+ uade_check_fix_string(um, 128);
+ strlcpy(gui_modulename, (char *) um->data, sizeof gui_modulename);
+ strlcpy(info->state.song->modulename, (char *) um->data, sizeof info->state.song->modulename);
+ break;
+
+ case UADE_REPLY_MSG:
+ uade_check_fix_string(um, 128);
+ trace ("Message: %s\n", (char *) um->data);
+ break;
+
+ case UADE_REPLY_PLAYERNAME:
+ uade_check_fix_string(um, 128);
+ strlcpy(gui_playername, (char *) um->data, sizeof gui_playername);
+ strlcpy(info->state.song->playername, (char *) um->data, sizeof info->state.song->playername);
+ break;
+
+ case UADE_REPLY_SONG_END:
+ if (um->size < 9) {
+ fprintf(stderr, "Invalid song end reply\n");
+ exit(-1);
+ }
+ tailbytes = ntohl(((uint32_t *) um->data)[0]);
+ /* next ntohl() is only there for a principle. it is not useful */
+ if (ntohl(((uint32_t *) um->data)[1]) == 0) {
+ /* normal happy song end. go to next subsong if any */
+ info->subsong_end = 1;
+ } else {
+ /* unhappy song end (error in the 68k side). skip to next song
+ ignoring possible subsongs */
+ info->song_end_trigger = 1;
+ }
+ i = 0;
+ reason = (char *) &um->data[8];
+ while (reason[i] && i < (um->size - 8))
+ i++;
+ if (reason[i] != 0 || (i != (um->size - 9))) {
+ fprintf(stderr, "Broken reason string with song end notice\n");
+ exit(-1);
+ }
+ /* fprintf(stderr, "Song end (%s)\n", reason); */
+ break;
+
+ case UADE_REPLY_SUBSONG_INFO:
+ if (um->size != 12) {
+ fprintf(stderr, "subsong info: too short a message\n");
+ exit(-1);
+ }
+ u32ptr = (uint32_t *) um->data;
+ info->state.song->min_subsong = ntohl(u32ptr[0]);
+ info->state.song->max_subsong = ntohl(u32ptr[1]);
+ info->state.song->cur_subsong = ntohl(u32ptr[2]);
+
+ if (!(-1 <= info->state.song->min_subsong && info->state.song->min_subsong <= info->state.song->cur_subsong && info->state.song->cur_subsong <= info->state.song->max_subsong)) {
+ int tempmin = info->state.song->min_subsong, tempmax = info->state.song->max_subsong;
+ fprintf(stderr, "uade: The player is broken. Subsong info does not match with %s.\n", gui_filename);
+ info->state.song->min_subsong = tempmin <= tempmax ? tempmin : tempmax;
+ info->state.song->max_subsong = tempmax >= tempmin ? tempmax : tempmin;
+ if (info->state.song->cur_subsong > info->state.song->max_subsong)
+ info->state.song->max_subsong = info->state.song->cur_subsong;
+ else if (info->state.song->cur_subsong < info->state.song->min_subsong)
+ info->state.song->min_subsong = info->state.song->cur_subsong;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "Expected sound data. got %d.\n", um->msgtype);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+// try decode `size' bytes
+// return number of decoded bytes
+// or 0 on EOF/error
+static int
+uadeplug_read (DB_fileinfo_t *_info, char *bytes, int size) {
+ uade_info_t *info = (uade_info_t *)_info;
+ int samplesize = _info->fmt.channels * _info->fmt.bps / 8;
+ int initsize = size;
+
+ while (size > 0) {
+ if (info->remaining) {
+ int n = min (info->remaining, size);
+ memcpy (bytes, info->buffer, n);
+ bytes += n;
+ size -= n;
+ if (n < info->remaining) {
+ memmove (info->buffer, info->buffer + n, info->remaining-n);
+ info->remaining -= n;
+ break;
+ }
+ info->remaining = 0;
+ }
+
+ if (uadeplug_frame (info) < 0) {
+ return initsize-size;
+ }
+ }
+
+ _info->readpos += ((initsize-size) / samplesize) / (float)(_info->fmt.samplerate);
+ return initsize-size;
+}
+
+// seek to specified sample (frame)
+// return 0 on success
+// return -1 on failure
+static int
+uadeplug_seek_sample (DB_fileinfo_t *_info, int sample) {
+ uade_info_t *info = (uade_info_t *)_info;
+
+ _info->readpos = (float)sample / _info->fmt.samplerate;
+ return 0;
+}
+
+// seek to specified time in seconds
+// return 0 on success
+// return -1 on failure
+static int
+uadeplug_seek (DB_fileinfo_t *_info, float time) {
+ return uadeplug_seek_sample (_info, time * _info->fmt.samplerate);
+}
+
+static DB_playItem_t *
+uadeplug_insert (DB_playItem_t *after, const char *fname) {
+ uade_init ();
+
+ char modulename[PATH_MAX];
+ char playername[PATH_MAX];
+ char scorename[PATH_MAX];
+ char gui_module_filename[PATH_MAX];
+ char gui_player_filename[PATH_MAX];
+
+ struct uade_state state;
+ memset (&state, 0, sizeof (state));
+ state.config = config_backup;
+ state.validconfig = 1;
+
+ printf ("probing the file\n");
+ int ret = uade_is_our_file(fname, 0, &state);
+
+ if (!ret) {
+ trace ("not uade file\n");
+ return NULL;
+ }
+
+ strlcpy(modulename, fname, sizeof modulename);
+ trace ("modulename: %s\n", modulename);
+ strlcpy(gui_module_filename, fname, sizeof gui_module_filename);
+ trace ("gui_module_fname: %s\n", gui_module_filename);
+
+ snprintf(scorename, sizeof scorename, "%s/score", get_uade_base_conf_dir ());
+ trace ("scorename: %s\n", scorename);
+
+ if (strcmp(state.ep->playername, "custom") == 0) {
+ strlcpy(playername, modulename, sizeof playername);
+ modulename[0] = 0;
+ gui_module_filename[0] = 0;
+ } else {
+ snprintf(playername, sizeof playername, "%s/players/%s", get_uade_base_conf_dir (), state.ep->playername);
+ }
+ trace ("playername: %s\n", playername);
+
+ if (!uade_alloc_song(&state, fname)) {
+ trace ("uade_alloc_song fail\n");
+ return NULL;
+ }
+
+ uade_set_ep_attributes(&state);
+
+ uade_set_song_attributes(&state, playername, sizeof playername);
+
+ uade_set_effects(&state);
+
+ strlcpy(gui_player_filename, playername, sizeof gui_player_filename);
+
+ if (!state.pid) {
+ char configname[PATH_MAX];
+ snprintf(configname, sizeof configname, "%s/uaerc", UADE_CONFIG_BASE_DIR);
+ uade_spawn(&state, UADE_CONFIG_UADE_CORE, configname);
+ }
+
+ printf ("uade_song_initialization\n");
+ ret = uade_song_initialization(scorename, playername, modulename, &state);
+ if (ret) {
+ if (ret != UADECORE_CANT_PLAY && ret != UADECORE_INIT_ERROR) {
+ fprintf(stderr, "Can not initialize song. Unknown error.\n");
+ return NULL;
+ }
+ uade_unalloc_song(&state);
+ return NULL;
+ }
+ printf ("init done\n");
+ int minsong = uade_get_min_subsong (&state, 0);
+ int maxsong = uade_get_max_subsong (&state, 0);
+
+ char info[256];
+ int playtime = state.song->playtime;
+
+ /* Hack. Set info text and song length late because we didn't know
+ subsong amounts before this. Pass zero as a length so that the
+ graphical play time counter will run but seek is still enabled.
+ Passing -1 as playtime would disable seeking. */
+ if (playtime <= 0)
+ playtime = 0;
+
+ if (uade_generate_song_title(info, sizeof info, &state))
+ strlcpy(info, gui_filename, sizeof info);
+
+// playhandle->set_params(playhandle, info, playtime,
+// UADE_BYTES_PER_FRAME * state.config.frequency,
+// state.config.frequency, UADE_CHANNELS);
+
+
+ // no cuesheet, prepare track for addition
+ DB_playItem_t *it = deadbeef->pl_item_alloc ();
+ it->decoder_id = deadbeef->plug_get_decoder_id (plugin.plugin.id);
+ it->fname = strdup (fname);
+ it->filetype = filetypes[0];
+ float duration = -1;
+ if (playtime != -1) {
+ duration = playtime / 1000.f;
+ }
+ deadbeef->pl_set_item_duration (it, duration);
+
+ // title is empty, this call will set track title to filename without extension
+ const char *fn = strrchr (fname, '/');
+ if (fn) {
+ fn++;
+ }
+ else {
+ fn = fname;
+ }
+ deadbeef->pl_add_meta (it, "title", info);
+
+ // now the track is ready, insert into playlist
+ after = deadbeef->pl_insert_item (after, it);
+ deadbeef->pl_item_unref (it);
+ uade_unalloc_song(&state);
+ if (state.pid) {
+ kill(state.pid, SIGTERM);
+ }
+ return after;
+}
+
+// define plugin interface
+static DB_decoder_t plugin = {
+ DB_PLUGIN_SET_API_VERSION
+ .plugin.version_major = 1,
+ .plugin.version_minor = 0,
+ .plugin.type = DB_PLUGIN_DECODER,
+ .plugin.id = "uade",
+ .plugin.name = "UADE player",
+ .plugin.descr = "amiga module player based on UADE (http://zakalwe.fi/uade/)",
+ .plugin.author = "Alexey Yakovenko",
+ .plugin.email = "waker@users.sourceforge.net",
+ .plugin.website = "http://deadbeef.sf.net",
+ .open = uadeplug_open,
+ .init = uadeplug_init,
+ .free = uadeplug_free,
+ .read = uadeplug_read,
+ .seek = uadeplug_seek,
+ .seek_sample = uadeplug_seek_sample,
+ .insert = uadeplug_insert,
+ .exts = exts,
+ .prefixes = prefixes,
+ .filetypes = filetypes
+};
+
+DB_plugin_t *
+ddb_uade2_load (DB_functions_t *api) {
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}