/* SID plugin for DeaDBeeF Player Copyright (C) 2009-2014 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, see . */ #include #include #include #include #ifdef HAVE_CONFIG_H # include #endif #include "sidplay/sidplay2.h" #include "sidplay/builders/resid.h" //#include "md5.h" // #include "sidplay/sidendian.h" #include "../../deadbeef.h" #include "csid.h" extern DB_decoder_t sid_plugin; //#define trace(...) { fprintf(stderr, __VA_ARGS__); } #define trace(fmt,...) DB_functions_t *deadbeef; #define min(x,y) ((x)<(y)?(x):(y)) #define max(x,y) ((x)>(y)?(x):(y)) typedef struct { DB_fileinfo_t info; sidplay2 *sidplay; ReSIDBuilder *resid; SidTune *tune; float duration; // of the current song } sid_info_t; static inline void le_int16 (int16_t in, unsigned char *out) { char *pin = (char *)∈ #if !WORDS_BIGENDIAN out[0] = pin[0]; out[1] = pin[1]; #else out[1] = pin[0]; out[0] = pin[1]; #endif } // SLDB support costs ~1M!!! // current hvsc sldb size is ~35k songs #define SLDB_MAX_SONGS 40000 // ~50k subsongs in current sldb #define SLDB_POOL_SIZE 55000 typedef struct { uint8_t sldb_digests[SLDB_MAX_SONGS][16]; int16_t sldb_pool[SLDB_POOL_SIZE]; int sldb_poolmark; int16_t *sldb_lengths[SLDB_MAX_SONGS]; int sldb_size; } sldb_t; static int sldb_loaded; static sldb_t *sldb; static int sldb_disable; static int chip_voices = 0xff; static int chip_voices_changed = 0; static void sldb_load() { if (sldb_disable) { return; } trace ("sldb_load\n"); int conf_hvsc_enable = deadbeef->conf_get_int ("hvsc_enable", 0); if (sldb_loaded || !conf_hvsc_enable) { sldb_disable = 1; return; } char conf_hvsc_path[1000]; deadbeef->conf_get_str ("hvsc_path", "", conf_hvsc_path, sizeof (conf_hvsc_path)); if (!conf_hvsc_path[0]) { sldb_disable = 1; return; } sldb_loaded = 1; const char *fname = conf_hvsc_path; FILE *fp = fopen (fname, "r"); if (!fp) { trace ("sid: failed to open file %s\n", fname); sldb_disable = 1; return; } char str[1024]; int line = 1; if (fgets (str, 1024, fp) != str) { goto fail; // eof } if (strncmp (str, "[Database]", 10)) { goto fail; // bad format } if (!sldb) { sldb = (sldb_t *)malloc (sizeof (sldb_t)); memset (sldb, 0, sizeof (sldb_t)); } while (fgets (str, 1024, fp) == str) { if (sldb->sldb_size >= SLDB_MAX_SONGS) { trace ("sldb loader ran out of memory.\n"); break; } line++; if (str[0] == ';') { // trace ("reading songlength for %s", str); continue; // comment } // read/validate md5 const char *p = str; uint8_t digest[16]; int sz = 0; char byte[3]; while (*p && sz < 16) { byte[0] = tolower (*p); if (!((byte[0] >= '0' && byte[0] <= '9') || (byte[0] >= 'a' && byte[0] <= 'f'))) { trace ("invalid byte 0 in md5: %c\n", byte[0]); break; } p++; if (!(*p)) { break; } byte[1] = tolower (*p); if (!((byte[1] >= '0' && byte[1] <= '9') || (byte[1] >= 'a' && byte[1] <= 'f'))) { trace ("invalid byte 1 in md5: %c\n", byte[1]); break; } byte[2] = 0; p++; // convert from ascii hex to uint8_t if (byte[1] < 'a') { digest[sz] = byte[1] - '0'; } else { digest[sz] = byte[1] - 'a' + 10; } if (byte[0] < 'a') { digest[sz] |= (byte[0] - '0') << 4; } else { digest[sz] |= (byte[0] - 'a' + 10) << 4; } sz++; } if (sz < 16) { trace ("bad md5 (sz=%d, line=%d)\n", sz, line); continue; // bad song md5 } // else { // trace ("digest: "); // for (int j = 0; j < 16; j++) { // trace ("%02x", (int)digest[j]); // } // trace ("\n"); // exit (0); // } memcpy (sldb->sldb_digests[sldb->sldb_size], digest, 16); sldb->sldb_lengths[sldb->sldb_size] = &sldb->sldb_pool[sldb->sldb_poolmark]; sldb->sldb_size++; // check '=' sign if (*p != '=') { continue; // no '=' sign } p++; if (!(*p)) { continue; // unexpected eol } int subsong = 0; while (*p >= ' ') { // read subsong lengths until eol char timestamp[7]; // up to MMM:SS sz = 0; while (*p > ' ' && *p != '(' && sz < 7) { timestamp[sz++] = *p; p++; } if (sz < 4 || sz == 6 && *p > ' ' && *p != '(') { break; // bad timestamp } timestamp[sz] = 0; // check for unknown time int16_t time = -1; if (!strcmp (timestamp, "-:--")) { time = -1; } else { // parse timestamp const char *colon = strchr (timestamp, ':'); if (!colon) { break; // bad timestamp } // minute char minute[4]; strncpy (minute, timestamp, colon-timestamp); minute[colon-timestamp] = 0; // second char second[3]; strncpy (second, colon+1, 3); //trace ("subsong %d, time %s:%s\n", subsong, minute, second); time = atoi (minute) * 60 + atoi (second); } if (sldb->sldb_poolmark >= SLDB_POOL_SIZE) { trace ("sldb ran out of memory\n"); goto fail; } sldb->sldb_lengths[sldb->sldb_size-1][subsong] = time; sldb->sldb_poolmark++; subsong++; // prepare for next timestamp if (*p == '(') { // skip until next whitespace while (*p > ' ') { p++; } } if (*p < ' ') { break; // eol } // skip white spaces while (*p == ' ') { p++; } if (*p < ' ') { break; // eol } } } fail: sldb_disable = 1; fclose (fp); trace ("HVSC sldb loaded %d songs, %d subsongs total\n", sldb->sldb_size, sldb->sldb_poolmark); } static int sldb_find (const uint8_t *digest) { if (!sldb) { trace ("sldb not loaded\n"); return -1; } for (int i = 0; i < sldb->sldb_size; i++) { if (!memcmp (digest, sldb->sldb_digests[i], 16)) { return i; } } return -1; } DB_fileinfo_t * csid_open (uint32_t hints) { DB_fileinfo_t *_info = (DB_fileinfo_t *)malloc (sizeof (sid_info_t)); memset (_info, 0, sizeof (sid_info_t)); return _info; } static void csid_mute_voices (sid_info_t *info, int chip_voices) { int maxsids = info->sidplay->info ().maxsids; for (int k = 0; k < maxsids; k++) { sidemu *emu = info->resid->getsidemu (k); if (emu) { for (int i = 0; i < 3; i++) { bool mute = chip_voices & (1 << i) ? false : true; emu->voice (i, mute ? 0x00 : 0xff, mute); } } } } int csid_init (DB_fileinfo_t *_info, DB_playItem_t *it) { sid_info_t *info = (sid_info_t *)_info; // libsidplay crashes if file doesn't exist // so i have to check it here deadbeef->pl_lock (); DB_FILE *fp = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); if (!fp ){ return -1; } deadbeef->fclose (fp); info->sidplay = new sidplay2; info->resid = new ReSIDBuilder ("wtf"); info->resid->create (info->sidplay->info ().maxsids); info->resid->filter (true); int samplerate = deadbeef->conf_get_int ("sid.samplerate", 44100); int bps = deadbeef->conf_get_int ("sid.bps", 16); if (bps != 16 && bps != 8) { bps = 16; } info->resid->sampling (samplerate); info->duration = deadbeef->pl_get_item_duration (it); deadbeef->pl_lock (); info->tune = new SidTune (deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); info->tune->selectSong (deadbeef->pl_find_meta_int (it, ":TRACKNUM", 0)+1); sid2_config_t conf; conf = info->sidplay->config (); conf.frequency = samplerate; conf.precision = bps; conf.playback = info->tune->isStereo () ? sid2_stereo : sid2_mono; conf.sidEmulation = info->resid; conf.optimisation = 0; info->sidplay->config (conf); info->sidplay->load (info->tune); _info->plugin = &sid_plugin; _info->fmt.channels = conf.playback == sid2_stereo ? 2 : 1; _info->fmt.bps = bps; _info->fmt.samplerate = conf.frequency; _info->fmt.channelmask = _info->fmt.channels == 1 ? DDB_SPEAKER_FRONT_LEFT : (DDB_SPEAKER_FRONT_LEFT | DDB_SPEAKER_FRONT_RIGHT); _info->readpos = 0; chip_voices = deadbeef->conf_get_int ("chip.voices", 0xff); csid_mute_voices (info, chip_voices); return 0; } void csid_free (DB_fileinfo_t *_info) { sid_info_t *info = (sid_info_t *)_info; if (info) { delete info->sidplay; delete info->resid; delete info->tune; free (info); } } int csid_read (DB_fileinfo_t *_info, char *bytes, int size) { sid_info_t *info = (sid_info_t *)_info; if (_info->readpos > info->duration) { return 0; } if (chip_voices_changed) { chip_voices = deadbeef->conf_get_int ("chip.voices", 0xff); chip_voices_changed = 0; csid_mute_voices (info, chip_voices); } int rd = info->sidplay->play (bytes, size); int samplesize = (_info->fmt.bps>>3) * _info->fmt.channels; _info->readpos += rd / samplesize / (float)_info->fmt.samplerate; return size; } int csid_seek (DB_fileinfo_t *_info, float time) { sid_info_t *info = (sid_info_t *)_info; float t = time; if (t < _info->readpos) { // reinit info->sidplay->load (info->tune); } else { t -= _info->readpos; } info->resid->filter (false); int samples = t * _info->fmt.samplerate; samples *= (_info->fmt.bps>>3) * _info->fmt.channels; uint16_t buffer[2048 * _info->fmt.channels]; while (samples > 0) { int n = min (samples, 2048) * _info->fmt.channels; int done = info->sidplay->play (buffer, n); if (done < n) { trace ("sid seek failure\n"); return -1; } samples -= done; } info->resid->filter (true); _info->readpos = time; return 0; } static const char * convstr (const char* str, int sz, char *out, int out_sz) { const char *cs = deadbeef->junk_detect_charset (str); if (!cs) { return str; } else { if (deadbeef->junk_iconv (str, sz, out, out_sz, cs, "utf-8") >= 0) { return out; } } return NULL; } extern "C" DB_playItem_t * csid_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) { trace ("inserting %s\n", fname); sldb_load (); SidTune *tune; trace ("new SidTune\n"); tune = new SidTune (fname); int tunes = tune->getInfo ().songs; trace ("subtunes: %d\n", tunes); uint8_t sig[16]; unsigned char tmp[2]; #if 1 trace ("calculating md5\n"); DB_md5_t md5; deadbeef->md5_init (&md5); deadbeef->md5_append (&md5, (const uint8_t *)tune->cache.get () + tune->fileOffset, tune->getInfo ().c64dataLen); le_int16 (tune->getInfo ().initAddr, tmp); deadbeef->md5_append (&md5, tmp, 2); le_int16 (tune->getInfo ().playAddr, tmp); deadbeef->md5_append (&md5, tmp, 2); le_int16 (tune->getInfo ().songs, tmp); deadbeef->md5_append (&md5, tmp, 2); for (int s = 1; s <= tunes; s++) { tune->selectSong (s); // songspeed is uint8_t, so no need for byteswap deadbeef->md5_append (&md5, &tune->getInfo ().songSpeed, 1); } if (tune->getInfo ().clockSpeed == SIDTUNE_CLOCK_NTSC) { deadbeef->md5_append (&md5, &tune->getInfo ().clockSpeed, sizeof (tune->getInfo ().clockSpeed)); } deadbeef->md5_finish (&md5, sig); #else // md5 calc from libsidplay2 MD5 myMD5; myMD5.append ((const char *)tune->cache.get() + tune->fileOffset, tune->getInfo ().c64dataLen); // Include INIT and PLAY address. endian_little16 (tmp,tune->getInfo ().initAddr); myMD5.append (tmp,sizeof(tmp)); endian_little16 (tmp,tune->getInfo ().playAddr); myMD5.append (tmp,sizeof(tmp)); // Include number of songs. endian_little16 (tmp,tune->getInfo ().songs); myMD5.append (tmp,sizeof(tmp)); { // Include song speed for each song. for (uint_least16_t s = 1; s <= tune->getInfo ().songs; s++) { tune->selectSong (s); myMD5.append (&tune->getInfo ().songSpeed,1); } } // Deal with PSID v2NG clock speed flags: Let only NTSC // clock speed change the MD5 fingerprint. That way the // fingerprint of a PAL-speed sidtune in PSID v1, v2, and // PSID v2NG format is the same. if (tune->getInfo ().clockSpeed == SIDTUNE_CLOCK_NTSC) { myMD5.append (&tune->getInfo ().clockSpeed,sizeof(tune->getInfo ().clockSpeed)); } myMD5.finish (); memcpy (sig, myMD5.getDigest (), 16); #endif int song = -1; if (sldb_loaded) { song = sldb_find (sig); } trace ("inserting tunes...\n"); for (int s = 0; s < tunes; s++) { trace ("select %d...\n", s); if (tune->selectSong (s+1)) { DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, sid_plugin.plugin.id); deadbeef->pl_set_meta_int (it, ":TRACKNUM", s); SidTuneInfo sidinfo; tune->getInfo (sidinfo); int i = sidinfo.numberOfInfoStrings; int title_added = 0; trace ("set %d metainfo...\n", s); char temp[2048]; if (i >= 1 && sidinfo.infoString[0] && sidinfo.infoString[0][0]) { const char *meta; if (sidinfo.songs > 1) { meta = "album"; } else { meta = "title"; title_added = 1; } deadbeef->pl_add_meta (it, meta, convstr (sidinfo.infoString[0], strlen (sidinfo.infoString[0]), temp, sizeof (temp))); } if (i >= 2 && sidinfo.infoString[1] && sidinfo.infoString[1][0]) { deadbeef->pl_add_meta (it, "artist", convstr (sidinfo.infoString[1], strlen (sidinfo.infoString[1]), temp, sizeof (temp))); } if (i >= 3 && sidinfo.infoString[2] && sidinfo.infoString[2][0]) { deadbeef->pl_add_meta (it, "copyright", convstr (sidinfo.infoString[2], strlen (sidinfo.infoString[2]), temp, sizeof (temp))); } for (int j = 3; j < i; j++) { if (sidinfo.infoString[j] && sidinfo.infoString[j][0]) { deadbeef->pl_add_meta (it, "info", convstr (sidinfo.infoString[j], strlen (sidinfo.infoString[j]), temp, sizeof (temp))); } } char trk[10]; snprintf (trk, 10, "%d", s+1); deadbeef->pl_add_meta (it, "track", trk); if (!title_added) { deadbeef->pl_add_meta (it, "title", NULL); } float length = deadbeef->conf_get_float ("sid.defaultlength", 180); if (sldb_loaded) { if (song >= 0 && sldb->sldb_lengths[song][s] >= 0) { length = sldb->sldb_lengths[song][s]; } // if (song < 0) { // trace ("song %s not found in db, md5: ", fname); // for (int j = 0; j < 16; j++) { // trace ("%02x", (int)sig[j]); // } // trace ("\n"); // } } deadbeef->plt_set_item_duration (plt, it, length); deadbeef->pl_add_meta (it, ":FILETYPE", "SID"); after = deadbeef->plt_insert_item (plt, after, it); deadbeef->pl_item_unref (it); } } trace ("delete sidtune\n"); delete tune; return after; } static int sid_configchanged (void) { int conf_hvsc_enable = deadbeef->conf_get_int ("hvsc_enable", 0); int disable = !conf_hvsc_enable; if (disable != sldb_disable) { sldb_disable = disable; } // pick up new sldb filename in case it was changed if (sldb) { free (sldb); sldb = NULL; sldb_loaded = 0; } if (chip_voices != deadbeef->conf_get_int ("chip.voices", 0xff)) { chip_voices_changed = 1; } return 0; } int sid_message (uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) { switch (id) { case DB_EV_CONFIGCHANGED: sid_configchanged (); break; } return 0; } int csid_start (void) { return 0; } int csid_stop (void) { if (sldb) { free (sldb); sldb = NULL; } sldb_loaded = 0; return 0; } extern "C" DB_plugin_t * sid_load (DB_functions_t *api) { deadbeef = api; return DB_PLUGIN (&sid_plugin); }