/* DeaDBeeF - ultimate music player for GNU/Linux systems with X11 Copyright (C) 2009-2010 Alexey Yakovenko 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 #include #include "../../deadbeef.h" #include "ao.h" #include "eng_protos.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,...) DB_functions_t *deadbeef; static DB_decoder_t plugin; static const char * exts[] = { "psf", "psf2", "spu", "ssf", "qsf", "dsf", "minipsf", "minipsf2", "minissf", "miniqsf", "minidsf", NULL }; static const char *filetypes[] = { "PSF", "PSF2", "SPU", "SSF", "QSF", "DSF", NULL }; typedef struct { DB_fileinfo_t info; int currentsample; uint32 type; void *decoder; char *filebuffer; size_t filesize; char buffer[735*4]; // psf2 decoder only works with 735 samples buffer int remaining; int skipsamples; float duration; } aoplug_info_t; static DB_fileinfo_t * aoplug_open (void) { DB_fileinfo_t *_info = malloc (sizeof (aoplug_info_t)); aoplug_info_t *info = (aoplug_info_t *)_info; memset (info, 0, sizeof (aoplug_info_t)); return _info; } static int aoplug_init (DB_fileinfo_t *_info, DB_playItem_t *it) { aoplug_info_t *info = (aoplug_info_t *)_info; _info->bps = 16; _info->channels = 2; _info->samplerate = 44100; _info->readpos = 0; _info->plugin = &plugin; info->duration = deadbeef->pl_get_item_duration (it); DB_FILE *file = deadbeef->fopen (it->fname); if (!file) { trace ("psf: failed to fopen %s\n", it->fname); return -1; } info->filesize = deadbeef->fgetlength (file); info->filebuffer = malloc (info->filesize); if (!info->filebuffer) { fprintf(stderr, "psf: could not allocate %d bytes of memory\n", (int)info->filesize); deadbeef->fclose (file); return -1; } if (deadbeef->fread(info->filebuffer, 1, info->filesize, file) != info->filesize) { fprintf(stderr, "psf: file read error: %s\n", it->fname); deadbeef->fclose (file); return -1; } deadbeef->fclose (file); info->type = ao_identify (info->filebuffer); if (info->type < 0) { fprintf (stderr, "psf: ao_identify failed\n"); return -1; } info->decoder = ao_start (info->type, it->fname, (uint8 *)info->filebuffer, info->filesize); if (!info->decoder) { fprintf (stderr, "psf: ao_start failed\n"); return -1; } return 0; } static void aoplug_free (DB_fileinfo_t *_info) { aoplug_info_t *info = (aoplug_info_t *)_info; if (info) { if (info->filebuffer) { ao_stop (info->type, info->decoder); free (info->filebuffer); info->filebuffer = NULL; } free (info); } } static int aoplug_read_int16 (DB_fileinfo_t *_info, char *bytes, int size) { aoplug_info_t *info = (aoplug_info_t *)_info; // printf ("aoplug_read_int16 %d samples, curr %d, end %d\n", size/4, info->currentsample, (int)(info->duration * _info->samplerate)); if (info->currentsample >= info->duration * _info->samplerate) { return 0; } int initsize = size; while (size > 0) { if (info->remaining > 0) { if (info->skipsamples > 0) { int n = min (info->skipsamples, info->remaining); if (info->remaining > n) { memmove (info->buffer, info->buffer+n*4, (info->remaining - n)*4); } info->remaining -= n; info->skipsamples -= n; continue; } int n = size / 4; n = min (info->remaining, n); memcpy (bytes, info->buffer, n * 4); if (info->remaining > n) { memmove (info->buffer, info->buffer+n*4, (info->remaining - n)*4); } info->remaining -= n; bytes += n*4; size -= n*4; } if (!info->remaining) { ao_decode (info->type, info->decoder, (int16_t *)info->buffer, 735); info->remaining = 735; } } info->currentsample += (initsize-size) / (_info->channels * _info->bps/8); return initsize-size; } static int aoplug_seek_sample (DB_fileinfo_t *_info, int sample) { aoplug_info_t *info = (aoplug_info_t *)_info; if (sample > info->currentsample) { info->skipsamples = sample-info->currentsample; } else { // restart song ao_command (info->type, info->decoder, COMMAND_RESTART, 0); info->skipsamples = sample; } info->currentsample = sample; _info->readpos = (float)sample / _info->samplerate; return 0; } static int aoplug_seek (DB_fileinfo_t *_info, float time) { return aoplug_seek_sample (_info, time * _info->samplerate); } static void aoplug_add_meta (DB_playItem_t *it, const char *key, const char *value, const char *comment_title) { const char *res = NULL; char tmp[200]; // check utf8 if (deadbeef->junk_recode (value, strlen (value), tmp, sizeof (tmp), "utf-8") >= 0) { if (key) { deadbeef->pl_add_meta (it, key, value); } res = value; } // check shift-jis if (deadbeef->junk_recode (value, strlen (value), tmp, sizeof (tmp), "SHIFT-JIS") >= 0) { if (key) { deadbeef->pl_add_meta (it, key, tmp); } res = tmp; } if (res) { char s[1024]; snprintf (s, sizeof (s), "%s%s", comment_title, res); deadbeef->pl_append_meta (it, "comment", s); } } static DB_playItem_t * aoplug_insert (DB_playItem_t *after, const char *fname) { DB_FILE *fp = deadbeef->fopen (fname); if (!fp) { trace ("psf: failed to fopen %s\n", fname); return NULL; } size_t size = deadbeef->fgetlength (fp); char *buffer = malloc (size); if (!buffer) { deadbeef->fclose (fp); fprintf(stderr, "psf: could not allocate %d bytes of memory\n", (int)size); return NULL; } if (deadbeef->fread(buffer, 1, size, fp) != size) { deadbeef->fclose (fp); fprintf(stderr, "psf: file read error: %s\n", fname); return NULL; } deadbeef->fclose (fp); int type = ao_identify (buffer); if (type < 0) { free (buffer); return NULL; } void *dec = ao_start (type, fname, (uint8*)buffer, size); if (!dec) { free (buffer); return NULL; } ao_display_info info; memset (&info, 0, sizeof (info)); int have_info = 0; if (ao_get_info (type, dec, &info) == AO_SUCCESS) { have_info = 1; } ao_stop (type, dec); dec = NULL; free (buffer); DB_playItem_t *it = deadbeef->pl_item_alloc (); it->decoder_id = deadbeef->plug_get_decoder_id (plugin.plugin.id); it->fname = strdup (fname); const char *ext = fname + strlen (fname); while (*ext != '.' && ext > fname) { ext--; } if (*ext == '.') { ext++; if (!strcasecmp (ext, "psf") || !strcasecmp (ext, "minipsf")) { it->filetype = filetypes[0]; } else if (!strcasecmp (ext, "psf2") || !strcasecmp (ext, "minipsf2")) { it->filetype = filetypes[1]; } else if (!strcasecmp (ext, "spu")) { it->filetype = filetypes[2]; } else if (!strcasecmp (ext, "ssf") || !strcasecmp (ext, "minissf")) { it->filetype = filetypes[3]; } else if (!strcasecmp (ext, "dsf") || !strcasecmp (ext, "minidsf")) { it->filetype = filetypes[5]; } else if (!strcasecmp (ext, "qsf") || !strcasecmp (ext, "miniqsf")) { it->filetype = filetypes[4]; } } else { it->filetype = filetypes[0]; } float duration = 120; if (have_info) { int i; for (i = 1; i < 9; i++) { if (!strncasecmp (info.title[i], "Length: ", 8)) { int min, sec; if (sscanf (info.info[i], "%d:%d", &min, &sec) == 2) { duration = min * 60 + sec; } aoplug_add_meta (it, NULL, info.info[i], info.title[i]); } else if (!strncasecmp (info.title[i], "Name: ", 6) || !strncasecmp (info.title[i], "Song: ", 6)) { aoplug_add_meta (it, "title", info.info[i], info.title[i]); } else if (!strncasecmp (info.title[i], "Game: ", 6)) { aoplug_add_meta (it, "album", info.info[i], info.title[i]); } else if (!strncasecmp (info.title[i], "Artist: ", 8)) { aoplug_add_meta (it, "artist", info.info[i], info.title[i]); } else if (!strncasecmp (info.title[i], "Copyright: ", 11)) { aoplug_add_meta (it, "copyright", info.info[i], info.title[i]); } else if (!strncasecmp (info.title[i], "Year: ", 6)) { aoplug_add_meta (it, "year", info.info[i], info.title[i]); } else if (!strncasecmp (info.title[i], "Ripper: ", 8)) { aoplug_add_meta (it, "vendor", info.info[i], info.title[i]); } else { aoplug_add_meta (it, NULL, info.info[i], info.title[i]); } } } deadbeef->pl_set_item_duration (it, duration); deadbeef->pl_add_meta (it, "title", NULL); after = deadbeef->pl_insert_item (after, it); deadbeef->pl_item_unref (it); return after; } static int aoplug_start (void) { return 0; } static int aoplug_stop (void) { return 0; } static DB_decoder_t plugin = { DB_PLUGIN_SET_API_VERSION .plugin.version_major = 0, .plugin.version_minor = 1, .plugin.type = DB_PLUGIN_DECODER, .plugin.id = "psf", .plugin.name = "Audio Overload plugin", .plugin.descr = "psf, psf2, spu, ssf, minidsf player based on Audio Overload library", .plugin.author = "Alexey Yakovenko", .plugin.email = "waker@users.sourceforge.net", .plugin.website = "http://deadbeef.sf.net", .plugin.start = aoplug_start, .plugin.stop = aoplug_stop, .open = aoplug_open, .init = aoplug_init, .free = aoplug_free, .read_int16 = aoplug_read_int16, .seek = aoplug_seek, .seek_sample = aoplug_seek_sample, .insert = aoplug_insert, .exts = exts, .filetypes = filetypes }; DB_plugin_t * ao_load (DB_functions_t *api) { deadbeef = api; return DB_PLUGIN (&plugin); }