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