#include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif #include "plugin.h" #include "titlestring.h" #include "util.h" #include "id666.h" #include "openspc.h" #include "resample.h" #include "spc-about.h" #include "spc-config.h" #include "fileinfo.h" static InputPlugin spc_ip; #define OUTPUT_FREQ 44100 #define OSPC_CHANNELS 2 #define OSPC_FREQ 32000 #define BUF_SAMPLES (512*OSPC_CHANNELS) #define BUF_BYTES (BUF_SAMPLES * sizeof(short)) static void spc_init(void); static void spc_cleanup(void); static int spc_is_our_file(char *filename); static void spc_play(char *filename); static void spc_stop(void); static void spc_pause(short paused); static void spc_seek(int time); static int spc_get_time(void); static void spc_get_info(char *filename, char **title, int *length); InputPlugin *get_iplugin_info(void) { memset(&spc_ip, 0, sizeof(spc_ip)); spc_ip.description = "OpenSPC (" OPENSPC_VERSION ") Player " VERSION; spc_ip.init = spc_init; spc_ip.cleanup = spc_cleanup; spc_ip.about = spc_about; spc_ip.configure = spc_configure; spc_ip.is_our_file = spc_is_our_file; spc_ip.play_file = spc_play; spc_ip.stop = spc_stop; spc_ip.pause = spc_pause; spc_ip.seek = spc_seek; spc_ip.get_time = spc_get_time; spc_ip.get_song_info = spc_get_info; spc_ip.file_info_box = spc_fileinfo; return &spc_ip; } static int spc_is_our_file(char *filename) { char *ext; ext = strrchr(filename, '.'); if (ext && !strcasecmp(ext, ".spc")) return TRUE; return FALSE; } static void spc_get_info(char *filename, char **title, int *length) { int fd; id666_t info; TitleInput *input; char *dupfn, *fn, *ext; *title = "Error"; *length = -1; fd = open(filename, O_RDONLY); if (fd < 0) return; *title = NULL; XMMS_NEW_TITLEINPUT(input); if (!spc_read_id666(fd, &info)) { input->performer = info.artist; input->album_name = info.game_title; input->track_name = info.ost_title?info.ost_title:info.song_title; input->track_number = info.ost_track; input->year = info.year?info.year:(info.dumpdate / 10000); input->date = info.dumpdate?g_strdup_printf("%04d-%02d-%02d", info.dumpdate / 10000, (info.dumpdate / 100) % 100, info.dumpdate % 100):NULL; input->comment = info.comments; if (info.intro_length <= 0 && info.loop_length <= 0 && info.end_length <= 0 && info.fade_length <= 0) { info.loop_length = spc_cfg.default_loop_length; info.fade_length = spc_cfg.default_fade_length; } switch (spc_cfg.playtime_style) { case PTS_LOOP_COUNT: if (info.end_length > 0) { *length = info.intro_length + info.loop_length + info.end_length + info.fade_length; break; } *length = info.intro_length + info.loop_length*spc_cfg.loop_count + info.fade_length; break; case PTS_LOOP_MINTIME: if (info.end_length > 0) { *length = info.intro_length + info.loop_length + info.end_length + info.fade_length; break; } *length = info.intro_length + info.fade_length; while (*length < spc_cfg.loop_mintime) *length += info.loop_length; break; case PTS_LOOP_FOREVER: *length = 0; break; } if (*length <= 0) *length = -1; else *length /= 32; } input->genre = "snes"; dupfn = g_strdup(filename); fn = g_basename(dupfn); if (fn > dupfn) fn[-1] = '\0'; input->file_name = fn; ext = strrchr(fn, '.'); if (ext) *ext++ = '\0'; input->file_ext = ext; if (fn > dupfn) input->file_path = g_strdup_printf("%s" G_DIR_SEPARATOR_S, dupfn); else input->file_path = g_strdup("." G_DIR_SEPARATOR_S); /**/ *title = xmms_get_titlestring(xmms_get_gentitle_format(), input); if (!*title) *title = g_strdup(input->file_name); g_free(input->date); g_free(dupfn); g_free(input->file_path); g_free(input); spc_clear_id666(&info); close(fd); } /****************************************************************************/ static void spc_init(void) { spc_cfg_read(); } static void spc_cleanup(void) { } /****************************************************************************/ static struct { int going, eof; int seek_to; int fade_start; int fade_dist; int fade_end; char data[0x10200]; } *spc_p; static pthread_t play_thread; #define ELEMENTS(x) (sizeof(x) / sizeof(*(x))) /* This needs a serious rewrite */ static void *spc_play_loop(void *arg) { int i, dist, size; short *p; short cbuf[BUF_SAMPLES*2]; short ospcbuf[(ELEMENTS(cbuf) * OUTPUT_FREQ) / OSPC_FREQ]; SAMPLE fltbuf[ELEMENTS(ospcbuf)]; SAMPLE rebuf[ELEMENTS(cbuf)]; int ospc_eof; int pos; int chead, ccur; res_state resampler; reinit: ospc_eof = 0; pos = 0; chead = 0; ccur = 0; spc_ip.output->flush(0); if (!OSPC_Init(spc_p->data, sizeof(spc_p->data))) if (!res_init(&resampler, OSPC_CHANNELS, OUTPUT_FREQ, OSPC_FREQ, RES_END)) { while (spc_p->going) { if (spc_p->seek_to != -1) { dist = (spc_p->seek_to * 32) - pos; if (dist < 0) { res_clear(&resampler); goto reinit; } dist += pos; spc_ip.output->flush(dist / 32); spc_p->seek_to = -1; while (pos < dist && spc_p->going) { size = OSPC_Run(-1, ospcbuf, sizeof(ospcbuf)) / sizeof(*ospcbuf); pos += size / OSPC_CHANNELS; } if (!spc_p->going) break; } if (spc_p->eof) { xmms_usleep(10000); continue; } size = 0; if (!ospc_eof) { size = res_push_max_input(&resampler, (ELEMENTS(cbuf) - ccur) / OSPC_CHANNELS) * OSPC_CHANNELS; /* This check isn't really needed. */ if (size > ELEMENTS(ospcbuf)) size = ELEMENTS(ospcbuf); size = OSPC_Run(-1, ospcbuf, size*sizeof(*ospcbuf)) / sizeof(*ospcbuf); if (spc_p->fade_start > 0 && spc_p->fade_dist > 0 && (pos + size/OSPC_CHANNELS) > spc_p->fade_start) { for (i = 0; i < size; i++) { dist = (pos + i/OSPC_CHANNELS) - spc_p->fade_start; if (dist < 0) { fltbuf[i] = ospcbuf[i] / 32768.0; continue; } dist = spc_p->fade_dist - dist; fltbuf[i] = (ospcbuf[i] / 32768.0)*(SAMPLE)dist / (SAMPLE)spc_p->fade_dist; } } else { for (i = 0; i < size; i++) fltbuf[i] = ospcbuf[i] / 32768.0; } pos += size / OSPC_CHANNELS; if (spc_p->fade_end > 0 && pos >= spc_p->fade_end) { ospc_eof = 1; size = (pos - spc_p->fade_end); } size = res_push_interleaved(&resampler, rebuf, (const float*)fltbuf, size / OSPC_CHANNELS) * OSPC_CHANNELS; } else if (ccur < BUF_SAMPLES && ospc_eof < 2) { ospc_eof = 2; /* let's not drain it too much, now :) */ size = res_drain_interleaved(&resampler, rebuf); } /* size is set by now to the number of samples in rebuf */ dist = ELEMENTS(cbuf) - (chead + ccur); if (dist <= 0) { dist = 2*ELEMENTS(cbuf) - (chead + ccur); p = &cbuf[chead + ccur - ELEMENTS(cbuf)]; } else { p = &cbuf[chead + ccur]; } if (dist < size) { for (i = 0; i < dist; i++) *p++ = rebuf[i] * 32767; p = cbuf; for (; i < size; i++) *p++ = rebuf[i] * 32767; } else { for (i = 0; i < size; i++) *p++ = rebuf[i] * 32767; } ccur += size; /**/ size = ccur; if (size > BUF_SAMPLES) size = BUF_SAMPLES; size *= sizeof(short); spc_ip.add_vis_pcm(spc_ip.output->written_time(), FMT_S16_NE, OSPC_CHANNELS, size, &cbuf[chead]); while (spc_ip.output->buffer_free() < size && spc_p->going) xmms_usleep(10000); if (spc_p->going) spc_ip.output->write_audio(&cbuf[chead], size); size /= sizeof(short); ccur -= size; chead += size; if (chead >= ELEMENTS(cbuf)) chead -= ELEMENTS(cbuf); if (ospc_eof && !ccur) { spc_p->eof = TRUE; spc_ip.output->buffer_free(); spc_ip.output->buffer_free(); } } res_clear(&resampler); } pthread_exit(NULL); } static void spc_play(char *filename) { int fd, length; char *title; id666_t info; ssize_t len; if (spc_p) return; spc_p = g_malloc0(sizeof(*spc_p)); fd = open(filename, O_RDONLY); if (fd < 0) return; len = read(fd, spc_p->data, sizeof(spc_p->data)); spc_read_id666(fd, &info); close(fd); if (len != sizeof(spc_p->data)) return; if (!spc_ip.output->open_audio(FMT_S16_NE, OUTPUT_FREQ, OSPC_CHANNELS)) return; spc_p->going = TRUE; spc_p->eof = FALSE; if (info.intro_length <= 0 && info.loop_length <= 0 && info.end_length <= 0 && info.fade_length <= 0) { info.loop_length = spc_cfg.default_loop_length; info.fade_length = spc_cfg.default_fade_length; } switch (spc_cfg.playtime_style) { case PTS_LOOP_COUNT: if (info.end_length > 0) { spc_p->fade_start = info.intro_length + info.loop_length + info.end_length; spc_p->fade_dist = info.fade_length; spc_p->fade_end = spc_p->fade_start + spc_p->fade_dist; break; } spc_p->fade_start = info.intro_length + info.loop_length*spc_cfg.loop_count; spc_p->fade_dist = info.fade_length; spc_p->fade_end = spc_p->fade_start + spc_p->fade_dist; break; case PTS_LOOP_MINTIME: if (info.end_length > 0) { spc_p->fade_start = info.intro_length + info.loop_length + info.end_length; spc_p->fade_dist = info.fade_length; spc_p->fade_end = spc_p->fade_start + spc_p->fade_dist; break; } spc_p->fade_start = info.intro_length; spc_p->fade_dist = info.fade_length; spc_p->fade_end = spc_p->fade_start + spc_p->fade_dist; while (spc_p->fade_end < spc_cfg.loop_mintime) { spc_p->fade_start += info.loop_length; spc_p->fade_end += info.loop_length; } break; case PTS_LOOP_FOREVER: spc_p->fade_start = -1; spc_p->fade_dist = 0; spc_p->fade_end = -1; break; } spc_p->seek_to = -1; spc_get_info(filename, &title, &length); spc_ip.set_info(title, spc_p->fade_end>0?(spc_p->fade_end / 32):-1, -1, OUTPUT_FREQ, OSPC_CHANNELS); spc_clear_id666(&info); pthread_create(&play_thread, NULL, spc_play_loop, NULL); } static void spc_stop(void) { if (spc_p && spc_p->going) { spc_p->going = FALSE; pthread_join(play_thread, NULL); spc_ip.output->close_audio(); g_free(spc_p); spc_p = NULL; } } static void spc_pause(short pause) { spc_ip.output->pause(pause); } static void spc_seek(int time) { if (!spc_p) return; spc_p->seek_to = time * 1000; } static int spc_get_time(void) { if (!spc_p) return -1; if (!spc_p->going || (spc_p->eof && !spc_ip.output->buffer_playing())) return -1; return spc_ip.output->output_time(); }