1 /*
2     in_sc86 (Atari ST SNDH YM2149) input plugin for deadbeef
3     Copyright (C) 2015 Alexey Yakovenko
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 <string.h>
25 #include <stdlib.h>
26 #include <limits.h>
27 #include "../../deadbeef.h"
28 #include "sc68/sc68.h"
29 
30 #define trace(...) { fprintf(stderr, __VA_ARGS__); }
31 
32 #define min(x,y) ((x)<(y)?(x):(y))
33 
34 static DB_decoder_t plugin;
35 static DB_functions_t *deadbeef;
36 
37 typedef struct {
38     DB_fileinfo_t info;
39     sc68_t *sc68;
40     int trk;
41     int loop;
42     uint64_t currentsample;
43     uint64_t totalsamples;
44 } in_sc68_info_t;
45 
46 static const char * exts[] = { "sndh", "snd", "sc68", NULL };
47 
48 // allocate codec control structure
49 static DB_fileinfo_t *
in_sc68_open(uint32_t hints)50 in_sc68_open (uint32_t hints) {
51     DB_fileinfo_t *_info = malloc (sizeof (in_sc68_info_t));
52     in_sc68_info_t *info = (in_sc68_info_t *)_info;
53     memset (info, 0, sizeof (in_sc68_info_t));
54     return _info;
55 }
56 
57 // prepare to decode the track, fill in mandatory plugin fields
58 // return -1 on failure
59 static int
in_sc68_init(DB_fileinfo_t * _info,DB_playItem_t * it)60 in_sc68_init (DB_fileinfo_t *_info, DB_playItem_t *it) {
61     in_sc68_info_t *info = (in_sc68_info_t *)_info;
62 
63     info->sc68 = sc68_create(0);
64     if (!info->sc68) {
65         return -1;
66     }
67 
68     // Load an sc68 file.
69     deadbeef->pl_lock ();
70     const char *fname = deadbeef->pl_find_meta (it, ":URI");
71     int res = sc68_load_uri(info->sc68, fname);
72     deadbeef->pl_unlock ();
73 
74     if (res) {
75         return -1;
76     }
77 
78     info->trk = deadbeef->pl_find_meta_int (it, ":TRACKNUM", 0);
79     sc68_music_info_t ti;
80     res = sc68_music_info (info->sc68, &ti, info->trk+1, 0);
81     if (res < 0) {
82         return -1;
83     }
84     info->loop = ti.trk.time_ms == 0;
85 
86     int samplerate = deadbeef->conf_get_int ("c68.samplerate", 44100);
87 //    sc68_cntl (info->sc68, SC68_SET_OPT_STR, "sampling_rate", samplerate);
88     if (ti.trk.time_ms > 0) {
89         info->totalsamples = (uint64_t)ti.trk.time_ms * samplerate / 1000;
90     }
91     else {
92         info->totalsamples = deadbeef->conf_get_float ("c68.songlength", 2) * 60 * samplerate;
93     }
94 
95     _info->plugin = &plugin;
96     _info->fmt.bps = 16;
97     _info->fmt.channels = 2;
98     _info->fmt.samplerate = samplerate;
99     _info->fmt.channelmask = DDB_SPEAKER_FRONT_LEFT | DDB_SPEAKER_FRONT_RIGHT;
100     _info->readpos = 0;
101 
102     sc68_play (info->sc68, info->trk+1, info->loop);
103     return 0;
104 }
105 
106 // free everything allocated in _init
107 static void
in_sc68_free(DB_fileinfo_t * _info)108 in_sc68_free (DB_fileinfo_t *_info) {
109     in_sc68_info_t *info = (in_sc68_info_t *)_info;
110     if (info) {
111         if (info->sc68) {
112             sc68_destroy (info->sc68);
113         }
114         free (info);
115     }
116 }
117 
118 
119 // try decode `size' bytes
120 // return number of decoded bytes
121 // or 0 on EOF/error
122 static int
in_sc68_read(DB_fileinfo_t * _info,char * bytes,int size)123 in_sc68_read (DB_fileinfo_t *_info, char *bytes, int size) {
124     in_sc68_info_t *info = (in_sc68_info_t *)_info;
125     if (info->currentsample >= info->totalsamples) {
126         return 0;
127     }
128     info->currentsample += size / (_info->fmt.channels * _info->fmt.bps/8);
129     int initsize = size;
130     while (size > 0) {
131         int n = size>>2;
132         int res = sc68_process(info->sc68, bytes, &n);
133         if (res & SC68_END) {
134             break;
135         }
136         size -= n<<2;
137     }
138     return initsize-size;
139 }
140 
141 // seek to specified sample (frame)
142 // return 0 on success
143 // return -1 on failure
144 static int
in_sc68_seek_sample(DB_fileinfo_t * _info,int sample)145 in_sc68_seek_sample (DB_fileinfo_t *_info, int sample) {
146     in_sc68_info_t *info = (in_sc68_info_t *)_info;
147 
148     if (sample < info->currentsample) {
149         sc68_stop (info->sc68);
150         sc68_play (info->sc68, info->trk+1, info->loop);
151         info->currentsample = 0;
152     }
153 
154     char buffer[512*4];
155     while (info->currentsample < sample) {
156         int sz = (int)(sample - info->currentsample);
157         sz = min (sz, sizeof (buffer)>>2);
158         int res = sc68_process(info->sc68, buffer, &sz);
159         if (res & SC68_END) {
160             break;
161         }
162         info->currentsample += sz;
163     }
164     _info->readpos = (float)info->currentsample / _info->fmt.samplerate;
165     return 0;
166 }
167 
168 // seek to specified time in seconds
169 // return 0 on success
170 // return -1 on failure
171 static int
in_sc68_seek(DB_fileinfo_t * _info,float time)172 in_sc68_seek (DB_fileinfo_t *_info, float time) {
173     return in_sc68_seek_sample (_info, time * _info->fmt.samplerate);
174 }
175 
176 static void
in_c68_meta_from_music_info(DB_playItem_t * it,sc68_music_info_t * ti,int trk)177 in_c68_meta_from_music_info (DB_playItem_t *it, sc68_music_info_t *ti, int trk) {
178     deadbeef->pl_delete_all_meta (it);
179 
180     const char *ft = "sc68";
181 
182     deadbeef->pl_replace_meta (it, ":FILETYPE", ft);
183     // add metainfo
184     if (!ti->title || !ti->title[0]) {
185         // title is empty, this call will set track title to filename without extension
186         deadbeef->pl_add_meta (it, "title", NULL);
187     }
188     else {
189         deadbeef->pl_add_meta (it, "title", ti->title);
190     }
191 
192     if (ti->artist && ti->artist[0]) {
193         deadbeef->pl_add_meta (it, "artist", ti->artist);
194     }
195 
196     if (ti->album && ti->album[0]) {
197         deadbeef->pl_add_meta (it, "album", ti->album);
198     }
199 
200     if (ti->genre && ti->genre[0]) {
201         deadbeef->pl_add_meta (it, "genre", ti->genre);
202     }
203 
204     if (ti->year && ti->year[0]) {
205         deadbeef->pl_add_meta (it, "year", ti->year);
206     }
207 
208     if (ti->format && ti->format[0]) {
209         deadbeef->pl_add_meta (it, "SC68_FORMAT", ti->format);
210     }
211 
212     if (ti->ripper && ti->ripper[0]) {
213         deadbeef->pl_add_meta (it, "SC68_RIPPER", ti->ripper);
214     }
215 
216     if (ti->converter && ti->converter[0]) {
217         deadbeef->pl_add_meta (it, "SC68_CONVERTER", ti->converter);
218     }
219 
220     deadbeef->pl_set_meta_int (it, ":TRACKNUM", trk);
221 }
222 
223 // read information from the track
224 // load/process cuesheet if exists
225 // insert track into playlist
226 // return track pointer on success
227 // return NULL on failure
228 
229 static DB_playItem_t *
in_sc68_insert(ddb_playlist_t * plt,DB_playItem_t * after,const char * fname)230 in_sc68_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) {
231 #if 0
232     // open file
233     DB_FILE *fp = deadbeef->fopen (fname);
234     if (!fp) {
235         trace ("in_sc68: failed to fopen %s\n", fname);
236         return NULL;
237     }
238 #endif
239 
240     sc68_t *sc68;
241     if (sc68 = sc68_create(0), !sc68) {
242         return NULL;
243     }
244 
245     // Load an sc68 file.
246     if (sc68_load_uri(sc68, fname)) {
247         return NULL;
248     }
249 
250     sc68_music_info_t di;
251     memset (&di, 0, sizeof (di));
252     int err = sc68_music_info (sc68, &di, 0, 0);
253     if (err < 0) {
254         sc68_destroy (sc68);
255         return NULL;
256     }
257 
258     int samplerate = deadbeef->conf_get_int ("c68.samplerate", 44100);
259     for (int tr = 0; tr < di.tracks; tr++) {
260         sc68_music_info_t ti;
261         memset (&ti, 0, sizeof (ti));
262         int err = sc68_music_info (sc68, &ti, tr+1, 0);
263         if (err < 0) {
264             continue;
265         }
266 
267         uint64_t totalsamples;
268         if (ti.trk.time_ms > 0) {
269             totalsamples = (uint64_t)ti.trk.time_ms * samplerate / 1000;
270         }
271         else {
272             totalsamples = deadbeef->conf_get_float ("c68.songlength", 2) * 60 * samplerate;
273         }
274 
275         DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id);
276 
277         deadbeef->plt_set_item_duration (plt, it, (float)totalsamples/samplerate);
278 
279         in_c68_meta_from_music_info (it, &ti, tr);
280 
281         // now the track is ready, insert into playlist
282         after = deadbeef->plt_insert_item (plt, after, it);
283         deadbeef->pl_item_unref (it);
284     }
285 
286     sc68_destroy (sc68);
287     return after;
288 }
289 
290 static int
in_sc68_read_metadata(DB_playItem_t * it)291 in_sc68_read_metadata (DB_playItem_t *it) {
292     sc68_t *sc68 = sc68_create(0);
293     if (!sc68) {
294         return -1;
295     }
296 
297     // Load an sc68 file.
298     deadbeef->pl_lock ();
299     const char *fname = deadbeef->pl_find_meta (it, ":URI");
300     int res = sc68_load_uri (sc68, fname);
301     deadbeef->pl_unlock ();
302 
303     if (res) {
304         return -1;
305     }
306 
307     int trk = deadbeef->pl_find_meta_int (it, ":TRACKNUM", 0);
308     sc68_music_info_t ti;
309     res = sc68_music_info (sc68, &ti, trk+1, 0);
310     if (res < 0) {
311         sc68_destroy (sc68);
312         return -1;
313     }
314 
315     in_c68_meta_from_music_info (it, &ti, trk);
316 
317     return 0;
318 }
319 
320 static int
in_sc68_start(void)321 in_sc68_start (void) {
322     // do one-time plugin initialization here
323     // e.g. starting threads for background processing, subscribing to events, etc
324     // return 0 on success
325     // return -1 on failure
326 
327     if (sc68_init(0)) {
328         sc68_shutdown ();
329         return -1;
330     }
331 
332     char datadir[PATH_MAX];
333     snprintf (datadir, sizeof (datadir), "%s/data68", deadbeef->get_plugin_dir ());
334     sc68_cntl (0, SC68_SET_OPT_STR, "share-path", datadir);
335 
336     return 0;
337 }
338 
339 static int
in_sc68_stop(void)340 in_sc68_stop (void) {
341     // undo everything done in _start here
342     // return 0 on success
343     // return -1 on failure
344     sc68_shutdown ();
345     return 0;
346 }
347 
348 static const char settings_dlg[] =
349 "property \"Default song length (in minutes)\" entry c68.songlength 2;\n" // 0..1440
350 "property \"Samplerate\" entry c68.samplerate 44100;\n" // 6000-50000
351 "property \"Skip when shorter than (sec)\" entry c68.skip_time 4;\n" // 4..86400
352 ;
353 
354 // define plugin interface
355 static DB_decoder_t plugin = {
356     DB_PLUGIN_SET_API_VERSION
357     .plugin.version_major = 0,
358     .plugin.version_minor = 1,
359     .plugin.type = DB_PLUGIN_DECODER,
360     .plugin.name = "SC68 player (Atari ST SNDH YM2149)",
361     .plugin.id = "in_sc68",
362     .plugin.descr = "SC68 player (Atari ST SNDH YM2149)",
363     .plugin.copyright =
364         "in_sc86 (Atari ST SNDH YM2149) input plugin for deadbeef\n"
365         "Copyright (C) 2015 Alexey Yakovenko\n"
366         "based on sc68 library, see below for more information\n"
367         "\n"
368         "This software is provided 'as-is', without any express or implied\n"
369         "warranty.  In no event will the authors be held liable for any damages\n"
370         "arising from the use of this software.\n"
371         "\n"
372         "Permission is granted to anyone to use this software for any purpose,\n"
373         "including commercial applications, and to alter it and redistribute it\n"
374         "freely, subject to the following restrictions:\n"
375         "\n"
376         "1. The origin of this software must not be misrepresented; you must not\n"
377         " claim that you wrote the original software. If you use this software\n"
378         " in a product, an acknowledgment in the product documentation would be\n"
379         " appreciated but is not required.\n"
380         "\n"
381         "2. Altered source versions must be plainly marked as such, and must not be\n"
382         " misrepresented as being the original software.\n"
383         "\n"
384         "3. This notice may not be removed or altered from any source distribution.\n"
385         "\n"
386         "\n"
387         "\n"
388         "sc68 - Atari ST and Amiga music player\n"
389         "Copyright (C) 1998-2015  Benjamin Gerard\n"
390         "<ben@sashipa.com>\n"
391         "<http://sashipa.ben.free.fr/sc68/>\n"
392         "<http://sourceforge.net/projects/sc68/>\n"
393         "\n"
394         "This program is free software: you can redistribute it and/or\n"
395         "modify it under the terms of the GNU General Public License as\n"
396         "published by the Free Software Foundation, either version 3 of the\n"
397         "License, or (at your option) any later version.\n"
398         "\n"
399         "This program is distributed in the hope that it will be useful, but\n"
400         "WITHOUT ANY WARRANTY; without even the implied warranty of\n"
401         "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n"
402         "General Public License for more details.\n"
403         "\n"
404         "You should have received a copy of the GNU General Public License\n"
405         "along with this program.\n"
406         "\n"
407         "If not, see <http://www.gnu.org/licenses/>.\n",
408     .plugin.start = in_sc68_start,
409     .plugin.stop = in_sc68_stop,
410     .plugin.configdialog = settings_dlg,
411     .open = in_sc68_open,
412     .init = in_sc68_init,
413     .free = in_sc68_free,
414     .read = in_sc68_read,
415     .seek = in_sc68_seek,
416     .seek_sample = in_sc68_seek_sample,
417     .read_metadata = in_sc68_read_metadata,
418     .insert = in_sc68_insert,
419     .exts = exts,
420 };
421 
422 DB_plugin_t *
in_sc68_load(DB_functions_t * api)423 in_sc68_load (DB_functions_t *api) {
424     deadbeef = api;
425     return DB_PLUGIN (&plugin);
426 }
427 
428