1 /*
2     DeaDBeeF ADPLUG plugin
3     Copyright (C) 2009-2014 Alexey Yakovenko <waker@users.sourceforge.net>
4 
5     This software is provided 'as-is', without any express or implied
6     warranty.  In no event will the authors be held liable for any damages
7     arising from the use of this software.
8 
9     Permission is granted to anyone to use this software for any purpose,
10     including commercial applications, and to alter it and redistribute it
11     freely, subject to the following restrictions:
12 
13     1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17 
18     2. Altered source versions must be plainly marked as such, and must not be
19      misrepresented as being the original software.
20 
21     3. This notice may not be removed or altered from any source distribution.
22 */
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include "../../deadbeef.h"
28 #include "adplug.h"
29 #include "emuopl.h"
30 #include "kemuopl.h"
31 #include "surroundopl.h"
32 #include "silentopl.h"
33 
34 #define min(x,y) ((x)<(y)?(x):(y))
35 #define max(x,y) ((x)>(y)?(x):(y))
36 
37 //#define trace(...) { fprintf (stderr, __VA_ARGS__); }
38 #define trace(fmt,...)
39 
40 extern "C" {
41 
42 extern DB_decoder_t adplug_plugin;
43 DB_functions_t *deadbeef;
44 
45 const char *adplug_exts[] = { "A2M", "ADL", "AMD", "BAM", "CFF", "CMF", "D00", "DFM", "DMO", "DRO", "DTM", "HSC", "HSP", "IMF", "KSM", "LAA", "LDS", "M", "MAD", "MKJ", "MSC", "MTK", "RAD", "RAW", "RIX", "ROL", "S3M", "SA2", "SAT", "SCI", "SNG", "XAD", "XMS", "XSM", "JBM", NULL };
46 
47 const char *adplug_filetypes[] = { "A2M", "ADL", "AMD", "BAM", "CFF", "CMF", "D00", "DFM", "DMO", "DRO", "DTM", "HSC", "HSP", "IMF", "KSM", "LAA", "LDS", "M", "MAD", "MKJ", "MSC", "MTK", "RAD", "RAW", "RIX", "ROL", "S3M", "SA2", "SAT", "SCI", "SNG", "XAD", "XMS", "XSM", "JBM", NULL };
48 
49 
50 typedef struct {
51     DB_fileinfo_t info;
52     Copl *opl;
53     CPlayer *decoder;
54     int totalsamples;
55     int currentsample;
56     int subsong;
57     int toadd;
58 } adplug_info_t;
59 
60 DB_fileinfo_t *
adplug_open(uint32_t hints)61 adplug_open (uint32_t hints) {
62     adplug_info_t *info = (adplug_info_t *)malloc (sizeof (adplug_info_t));
63     DB_fileinfo_t *_info = (DB_fileinfo_t *)info;
64     memset (info, 0, sizeof (adplug_info_t));
65     return _info;
66 }
67 
68 int
adplug_init(DB_fileinfo_t * _info,DB_playItem_t * it)69 adplug_init (DB_fileinfo_t *_info, DB_playItem_t *it) {
70     // prepare to decode the track
71     // return -1 on failure
72     adplug_info_t *info = (adplug_info_t *)_info;
73 
74     int samplerate = deadbeef->conf_get_int ("synth.samplerate", 44100);
75     int bps = 16; // NOTE: there's no need to support 8bit input, because adplug simply downgrades 16bit signal to 8bits
76     int channels = 2;
77     if (deadbeef->conf_get_int ("adplug.surround", 1)) {
78         if (deadbeef->conf_get_int ("adplug.use_ken", 0)) {
79             Copl *a = new CKemuopl(samplerate, bps == 16, false);
80             Copl *b = new CKemuopl(samplerate, bps == 16, false);
81             info->opl = new CSurroundopl(a, b, bps == 16);
82         }
83         else {
84             Copl *a = new CEmuopl(samplerate, bps == 16, false);
85             Copl *b = new CEmuopl(samplerate, bps == 16, false);
86             info->opl = new CSurroundopl(a, b, bps == 16);
87         }
88     }
89     else {
90         if (deadbeef->conf_get_int ("adplug.use_satoh", 0)) {
91             info->opl = new CEmuopl (samplerate, bps == 16, channels == 2);
92         }
93         else {
94             info->opl = new CKemuopl (samplerate, bps == 16, channels == 2);
95         }
96     }
97     deadbeef->pl_lock ();
98     info->decoder = CAdPlug::factory (deadbeef->pl_find_meta (it, ":URI"), info->opl, CAdPlug::players);
99     deadbeef->pl_unlock ();
100     if (!info->decoder) {
101         trace ("adplug: failed to open %s\n", deadbeef->pl_find_meta (it, ":URI"));
102         return -1;
103     }
104 
105     info->subsong = deadbeef->pl_find_meta_int (it, ":TRACKNUM", 0);
106     info->decoder->rewind (info->subsong);
107     float dur = deadbeef->pl_get_item_duration (it);
108     info->totalsamples = dur * samplerate;
109     info->currentsample = 0;
110     info->toadd = 0;
111 
112     // fill in mandatory plugin fields
113     _info->plugin = &adplug_plugin;
114     _info->fmt.bps = bps;
115     _info->fmt.channels = channels;
116     _info->fmt.samplerate = samplerate;
117     _info->fmt.channelmask = _info->fmt.channels == 1 ? DDB_SPEAKER_FRONT_LEFT : (DDB_SPEAKER_FRONT_LEFT | DDB_SPEAKER_FRONT_RIGHT);
118     _info->readpos = 0;
119 
120     trace ("adplug_init ok (songlength=%d, duration=%f, totalsamples=%d)\n", info->decoder->songlength (info->subsong), deadbeef->pl_get_item_duration (it), info->totalsamples);
121 
122     return 0;
123 }
124 
125 void
adplug_free(DB_fileinfo_t * _info)126 adplug_free (DB_fileinfo_t *_info) {
127     // free everything allocated in _init
128     if (_info) {
129         adplug_info_t *info = (adplug_info_t *)_info;
130         if (info->decoder) {
131             delete info->decoder;
132         }
133         if (info->opl) {
134             delete info->opl;
135         }
136         free (info);
137     }
138 }
139 
140 int
adplug_read(DB_fileinfo_t * _info,char * bytes,int size)141 adplug_read (DB_fileinfo_t *_info, char *bytes, int size) {
142     // try decode `size' bytes
143     // return number of decoded bytes
144     // return 0 on EOF
145     adplug_info_t *info = (adplug_info_t *)_info;
146     bool playing = true;
147     int i;
148     int sampsize = (_info->fmt.bps / 8) * _info->fmt.channels;
149 
150     if (info->currentsample + size/sampsize >= info->totalsamples) {
151         // clip
152         size = (info->totalsamples - info->currentsample) * sampsize;
153         trace ("adplug: clipped to %d\n", size);
154         if (size <= 0) {
155             return 0;
156         }
157     }
158     int initsize = size;
159 
160     int towrite = size/sampsize;
161     char *sndbufpos = bytes;
162 
163 
164     while (towrite > 0)
165     {
166       while (info->toadd < 0)
167       {
168         info->toadd += _info->fmt.samplerate;
169         playing = info->decoder->update ();
170 //        decoder->time_ms += 1000 / plr.p->getrefresh ();
171       }
172       i = min (towrite, (long) (info->toadd / info->decoder->getrefresh () + sampsize) & ~(sampsize-1));
173       info->opl->update ((short *) sndbufpos, i);
174       sndbufpos += i * sampsize;
175       size -= i * sampsize;
176       info->currentsample += i;
177       towrite -= i;
178       info->toadd -= (long) (info->decoder->getrefresh () * i);
179     }
180     info->currentsample += size/4;
181     _info->readpos = (float)info->currentsample / _info->fmt.samplerate;
182     return initsize-size;
183 }
184 
185 int
adplug_seek_sample(DB_fileinfo_t * _info,int sample)186 adplug_seek_sample (DB_fileinfo_t *_info, int sample) {
187     // seek to specified sample (frame)
188     // return 0 on success
189     // return -1 on failure
190     adplug_info_t *info = (adplug_info_t *)_info;
191     if (sample >= info->totalsamples) {
192         trace ("adplug: seek outside bounds (%d of %d)\n", sample, info->totalsamples);
193         return -1;
194     }
195 
196     info->decoder->rewind (info->subsong);
197     info->currentsample = 0;
198 
199     while (info->currentsample < sample) {
200         info->decoder->update ();
201         int framesize = _info->fmt.samplerate / info->decoder->getrefresh ();
202         info->currentsample += framesize;
203     }
204 
205     if (info->currentsample >= info->totalsamples) {
206         return -1;
207     }
208 
209     info->toadd = 0;
210     trace ("adplug: new position after seek: %d of %d\n", info->currentsample, info->totalsamples);
211 
212     _info->readpos = (float)info->currentsample / _info->fmt.samplerate;
213 
214     return 0;
215 }
216 
217 int
adplug_seek(DB_fileinfo_t * _info,float time)218 adplug_seek (DB_fileinfo_t *_info, float time) {
219     // seek to specified time in seconds
220     // return 0 on success
221     // return -1 on failure
222     return adplug_seek_sample (_info, time * _info->fmt.samplerate);
223 }
224 
225 static const char *
adplug_get_extension(const char * fname)226 adplug_get_extension (const char *fname) {
227     const char *e = fname + strlen (fname);
228     while (*e != '.' && e != fname) {
229         e--;
230     }
231     if (*e == '.') {
232         e++;
233         // now find ext in list
234         for (int i = 0; adplug_exts[i]; i++) {
235             if (!strcasecmp (e, adplug_exts[i])) {
236                 return adplug_filetypes[i];
237             }
238         }
239     }
240     return "adplug-unknown";
241 }
242 
243 static void
adplug_add_meta(DB_playItem_t * it,const char * key,const char * value)244 adplug_add_meta (DB_playItem_t *it, const char *key, const char *value) {
245     if (!value) {
246         return;
247     }
248     const char *charset = deadbeef->junk_detect_charset (value);
249     if (charset) {
250         int l = strlen (value);
251         char str[1024];
252         deadbeef->junk_recode (value, l, str, sizeof (str), charset);
253         deadbeef->pl_add_meta (it, key, str);
254     }
255 }
256 
257 DB_playItem_t *
adplug_insert(ddb_playlist_t * plt,DB_playItem_t * after,const char * fname)258 adplug_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) {
259     // read information from the track
260     // load/process cuesheet if exists
261     // insert track into playlist
262     // return track pointer on success
263     // return NULL on failure
264 
265     trace ("adplug: trying to insert %s\n", fname);
266 
267     CSilentopl opl;
268     CPlayer *p = CAdPlug::factory (fname, &opl, CAdPlug::players);
269     if (!p) {
270         trace ("adplug: failed to open %s\n", fname);
271         return NULL;
272     }
273 
274     int subsongs = p->getsubsongs ();
275     for (int i = 0; i < subsongs; i++) {
276         // prepare track for addition
277         float dur = p->songlength (i)/1000.f;
278         if (dur < 0.1) {
279             continue;
280         }
281         DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, adplug_plugin.plugin.id);
282         deadbeef->pl_add_meta (it, ":FILETYPE", adplug_get_extension (fname));
283         deadbeef->pl_set_meta_int (it, ":TRACKNUM", i);
284         deadbeef->plt_set_item_duration (plt, it, dur);
285 #if 0
286         // add metainfo
287         if (p->gettitle()[0]) {
288             adplug_add_meta (it, "title", p->gettitle());
289         }
290         else {
291             deadbeef->pl_add_meta (it, "title", NULL);
292         }
293         if (p->getdesc()[0]) {
294             adplug_add_meta (it, "comment", p->getdesc());
295         }
296         if (!p->getauthor()[0]) {
297             adplug_add_meta (it, "artist", p->getauthor());
298         }
299 #endif
300         deadbeef->pl_add_meta (it, "title", NULL);
301         // insert
302         after = deadbeef->plt_insert_item (plt, after, it);
303         deadbeef->pl_item_unref (it);
304     }
305 
306     // free decoder
307     delete p;
308 
309     // now the track is ready, insert into playlist
310     return after;
311 }
312 
313 int
adplug_start(void)314 adplug_start (void) {
315     // do one-time plugin initialization here
316     // e.g. starting threads for background processing, subscribing to events, etc
317     // return 0 on success
318     // return -1 on failure
319     return 0;
320 }
321 
322 int
adplug_stop(void)323 adplug_stop (void) {
324     // undo everything done in _start here
325     // return 0 on success
326     // return -1 on failure
327     return 0;
328 }
329 
330 DB_plugin_t *
adplug_load(DB_functions_t * api)331 adplug_load (DB_functions_t *api) {
332     deadbeef = api;
333     return DB_PLUGIN (&adplug_plugin);
334 }
335 
336 }
337