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