apps/codec_thread.c | 7 + apps/codecs.c | 4 +- apps/codecs.h | 11 +- apps/codecs/adx.c | 2 +- apps/codecs/libtremor/SOURCES | 1 - apps/codecs/libtremor/ctype.c | 4 - apps/codecs/libtremor/info.c | 1 - apps/codecs/libtremor/vorbisfile.c | 1 - apps/codecs/mod.c | 11 +- apps/codecs/nsf.c | 13 +- apps/codecs/spc.c | 10 +- apps/codecs/wav.c | 4 +- apps/main.c | 6 +- apps/metadata.c | 8 +- apps/metadata.h | 2 +- apps/metadata/adx.c | 1 - apps/metadata/aiff.c | 1 - apps/metadata/ape.c | 1 - apps/metadata/asap.c | 1 - apps/metadata/asf.c | 1 - apps/metadata/flac.c | 1 - apps/metadata/metadata_common.c | 11 +- apps/metadata/monkeys.c | 1 - apps/metadata/mp4.c | 1 - apps/metadata/nsf.c | 1 - apps/metadata/ogg.c | 1 - apps/metadata/rm.c | 1 - apps/metadata/sid.c | 1 - apps/metadata/spc.c | 1 - apps/metadata/tta.c | 1 - apps/metadata/vorbis.c | 2 - apps/metadata/vox.c | 1 - apps/metadata/wavpack.c | 1 - apps/playback.c | 3 +- apps/plugins/test_codec.c | 9 +- apps/screen_access.c | 1 + apps/settings.h | 3 +- apps/tdspeed.c | 22 +- warble/Makefile | 63 +++ warble/autoconf.h | 16 + warble/warble.c | 824 ++++++++++++++++++++++++++++++++++++ 41 files changed, 974 insertions(+), 81 deletions(-) diff --git a/apps/codec_thread.c b/apps/codec_thread.c index a7bff74..3e18ce4 100644 --- a/apps/codec_thread.c +++ b/apps/codec_thread.c @@ -30,6 +30,7 @@ #include "buffering.h" #include "dsp.h" #include "metadata.h" +#include "settings.h" /* Define LOGF_ENABLE to enable logf output in this file */ /*#define LOGF_ENABLE*/ @@ -402,6 +403,11 @@ static enum codec_command_action } } +static bool codec_should_loop_callback(void) +{ + return global_settings.repeat_mode == REPEAT_ONE; +} + /* Initialize codec API */ void codec_init_codec_api(void) { @@ -418,6 +424,7 @@ void codec_init_codec_api(void) ci.set_offset = audio_codec_update_offset; ci.configure = codec_configure_callback; ci.get_command = codec_get_command_callback; + ci.should_loop = codec_should_loop_callback; } diff --git a/apps/codecs.c b/apps/codecs.c index cd4a9d5..c2efbaa 100644 --- a/apps/codecs.c +++ b/apps/codecs.c @@ -50,6 +50,7 @@ #include "sound.h" #include "splash.h" #include "general.h" +#include "rbpaths.h" #define LOGF_ENABLE #include "logf.h" @@ -97,6 +98,7 @@ struct codec_api ci = { NULL, /* set_offset */ NULL, /* configure */ NULL, /* get_command */ + NULL, /* should_loop */ /* kernel/ system */ #if defined(CPU_ARM) && CONFIG_PLATFORM & PLATFORM_NATIVE @@ -127,7 +129,6 @@ struct codec_api ci = { memmove, memcmp, memchr, - strcasestr, #if defined(DEBUG) || defined(SIMULATOR) debugf, #endif @@ -136,7 +137,6 @@ struct codec_api ci = { #endif (qsort_func)qsort, - &global_settings, #ifdef RB_PROFILE profile_thread, diff --git a/apps/codecs.h b/apps/codecs.h index e240811..a699711 100644 --- a/apps/codecs.h +++ b/apps/codecs.h @@ -44,7 +44,6 @@ #endif #include "dsp.h" #endif -#include "settings.h" #include "gcc_extensions.h" #include "load_code.h" @@ -75,12 +74,12 @@ #define CODEC_ENC_MAGIC 0x52454E43 /* RENC */ /* increase this every time the api struct changes */ -#define CODEC_API_VERSION 42 +#define CODEC_API_VERSION 43 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any new function which are "waiting" at the end of the function table) */ -#define CODEC_MIN_API_VERSION 42 +#define CODEC_MIN_API_VERSION 43 /* reasons for calling codec main entrypoint */ enum codec_entry_call_reason { @@ -145,6 +144,8 @@ struct codec_api { void (*configure)(int setting, intptr_t value); /* Obtain command action on what to do next */ enum codec_command_action (*get_command)(intptr_t *param); + /* Determine whether the loop should be used, if applicable. */ + bool (*should_loop)(void); /* kernel/ system */ #if defined(CPU_ARM) && CONFIG_PLATFORM & PLATFORM_NATIVE @@ -180,7 +181,6 @@ struct codec_api { void* (*memmove)(void *out, const void *in, size_t n); int (*memcmp)(const void *s1, const void *s2, size_t n); void *(*memchr)(const void *s1, int c, size_t n); - char *(*strcasestr) (const char* phaystack, const char* pneedle); #if defined(DEBUG) || defined(SIMULATOR) void (*debugf)(const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2); @@ -193,9 +193,6 @@ struct codec_api { void (*qsort)(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *)); - /* The ADX codec accesses global_settings to test for REPEAT_ONE mode */ - struct user_settings* global_settings; - #ifdef RB_PROFILE void (*profile_thread)(void); void (*profstop)(void); diff --git a/apps/codecs/adx.c b/apps/codecs/adx.c index e75e7dc..1318aff 100644 --- a/apps/codecs/adx.c +++ b/apps/codecs/adx.c @@ -239,7 +239,7 @@ enum codec_status codec_run(void) if (bufoff > end_adr-18*channels && looping) { DEBUGF("ADX: loop!\n"); /* check for endless looping */ - if (ci->global_settings->repeat_mode==REPEAT_ONE) { + if (ci->should_loop()) { loop_count=0; fade_count = -1; /* disable fade */ } else { diff --git a/apps/codecs/libtremor/SOURCES b/apps/codecs/libtremor/SOURCES index c622699..4076694 100644 --- a/apps/codecs/libtremor/SOURCES +++ b/apps/codecs/libtremor/SOURCES @@ -12,5 +12,4 @@ sharedbook.c synthesis.c vorbisfile.c window.c -ctype.c oggmalloc.c diff --git a/apps/codecs/libtremor/ctype.c b/apps/codecs/libtremor/ctype.c deleted file mode 100644 index 9f22047..0000000 --- a/apps/codecs/libtremor/ctype.c +++ /dev/null @@ -1,4 +0,0 @@ -#include "config.h" -#if (CONFIG_PLATFORM & PLATFORM_NATIVE) -#include "libc/ctype.c" -#endif diff --git a/apps/codecs/libtremor/info.c b/apps/codecs/libtremor/info.c index b21d08d..b3451d3 100644 --- a/apps/codecs/libtremor/info.c +++ b/apps/codecs/libtremor/info.c @@ -20,7 +20,6 @@ #include "config-tremor.h" #include -#include #include "ogg.h" #include "ivorbiscodec.h" #include "codec_internal.h" diff --git a/apps/codecs/libtremor/vorbisfile.c b/apps/codecs/libtremor/vorbisfile.c index 5721178..cdc1853 100644 --- a/apps/codecs/libtremor/vorbisfile.c +++ b/apps/codecs/libtremor/vorbisfile.c @@ -21,7 +21,6 @@ #include #include #include -#include "system.h" #include "ivorbiscodec.h" #include "ivorbisfile.h" diff --git a/apps/codecs/mod.c b/apps/codecs/mod.c index 3dfaac6..862dd86 100644 --- a/apps/codecs/mod.c +++ b/apps/codecs/mod.c @@ -37,7 +37,6 @@ #include #include #include -#include CODEC_HEADER @@ -1132,12 +1131,16 @@ void synthrender(int32_t *renderbuffer, int samplecount) if (modplayer.currentline == 64) { modplayer.patterntableposition++; - if (modplayer.patterntableposition >= modsong.songlength) + if (modplayer.patterntableposition >= modsong.songlength) { /* This is for Noise Tracker * modplayer.patterntableposition = * modsong.songendjumpposition; * More compatible approach is restart from 0 */ - modplayer.patterntableposition=0; + if (ci->should_loop()) + modplayer.patterntableposition = 0; + else + modplayer.patterntableposition = 127; + } modplayer.currentline = 0; } } @@ -1278,6 +1281,8 @@ enum codec_status codec_run(void) } if(old_patterntableposition != modplayer.patterntableposition) { + if (modplayer.patterntableposition == 127) + break; ci->set_elapsed(modplayer.patterntableposition*1000+500); old_patterntableposition=modplayer.patterntableposition; } diff --git a/apps/codecs/nsf.c b/apps/codecs/nsf.c index d626d52..334934f 100644 --- a/apps/codecs/nsf.c +++ b/apps/codecs/nsf.c @@ -4324,14 +4324,13 @@ jammed: static int track = 0; static char last_path[MAX_PATH]; static int dontresettrack = 0; -static bool repeat_one = false; static void set_codec_track(int t, int d) { int track,fade,def=0; SetTrack(t); - /* for REPEAT_ONE we disable track limits */ - if (!repeat_one) { + /* for loop mode we disable track limits */ + if (!ci->should_loop()) { if (!bIsExtended || nTrackTime[t]==-1) {track=60*2*1000; def=1;} else track=nTrackTime[t]; if (!bIsExtended || nTrackFade[t]==-1) fade=5*1000; @@ -4384,8 +4383,6 @@ enum codec_status codec_run(void) return CODEC_ERROR; } - repeat_one = ci->global_settings->repeat_mode == REPEAT_ONE; - init_nsf: if(!NSFCore_Initialize()) { DEBUGF("NSF: NSFCore_Initialize failed\n"); return CODEC_ERROR;} @@ -4401,7 +4398,7 @@ init_nsf: /* if this is the first time we're seeing this file, or if we haven't been asked to preserve the track number, default to the proper initial track */ - if (bIsExtended && !repeat_one && nPlaylistSize>0) { + if (bIsExtended && !ci->should_loop() && nPlaylistSize>0) { /* decide to use the playlist */ usingplaylist=1; track=0; @@ -4466,8 +4463,8 @@ init_nsf: print_timers(last_path,track); - if (repeat_one) { - /* in repeat one mode just advance to the next track */ + if (ci->should_loop()) { + /* in loop mode just advance to the next track */ track++; if (track>=nTrackCount) track=0; dontresettrack=1; diff --git a/apps/codecs/spc.c b/apps/codecs/spc.c index 6b21f9a..8974b8b 100644 --- a/apps/codecs/spc.c +++ b/apps/codecs/spc.c @@ -477,7 +477,7 @@ static int play_track( void ) sampleswritten += WAV_CHUNK_SIZE; /* is track timed? */ - if (ci->global_settings->repeat_mode!=REPEAT_ONE && ci->id3->length) { + if (!ci->should_loop() && ci->id3->length) { unsigned long curtime = sampleswritten*1000LL/SAMPLE_RATE; unsigned long lasttimesample = (sampleswritten-WAV_CHUNK_SIZE); @@ -513,10 +513,10 @@ static int play_track( void ) spc_play_send_samples(samples); - if (ci->global_settings->repeat_mode!=REPEAT_ONE) - ci->set_elapsed(sampleswritten*1000LL/SAMPLE_RATE); - else + if (ci->should_loop()) ci->set_elapsed(0); + else + ci->set_elapsed(sampleswritten*1000LL/SAMPLE_RATE); } EXIT_TIMER(total); @@ -569,7 +569,7 @@ enum codec_status codec_run(void) LoadID666(buffer+0x2e); - if (ci->global_settings->repeat_mode!=REPEAT_ONE && ID666.length==0) { + if (!ci->should_loop() && ID666.length==0) { ID666.length=3*60*1000; /* 3 minutes */ ID666.fade=5*1000; /* 5 seconds */ } diff --git a/apps/codecs/wav.c b/apps/codecs/wav.c index f6f83b1..d43961a 100644 --- a/apps/codecs/wav.c +++ b/apps/codecs/wav.c @@ -28,12 +28,12 @@ CODEC_HEADER /* WAVE (RIFF) codec: * * For a good documentation on WAVE files, see: - * http://www.tsp.ece.mcgill.ca/MMSP/Documents/AudioFormats/WAVE/WAVE.html + * http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html * and * http://www.sonicspot.com/guide/wavefiles.html * * For sample WAV files, see: - * http://www.tsp.ece.mcgill.ca/MMSP/Documents/AudioFormats/WAVE/Samples.html + * http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Samples.html * */ diff --git a/apps/main.c b/apps/main.c index 0b566b5..26c5bfa 100644 --- a/apps/main.c +++ b/apps/main.c @@ -407,7 +407,8 @@ static void init(void) scrobbler_init(); #if CONFIG_CODEC == SWCODEC && defined (HAVE_PITCHSCREEN) - tdspeed_init(); + if (global_settings.timestretch_enabled) + tdspeed_init(); #endif /* CONFIG_CODEC == SWCODEC */ audio_init(); @@ -670,7 +671,8 @@ static void init(void) filetype_init(); scrobbler_init(); #if CONFIG_CODEC == SWCODEC && defined (HAVE_PITCHSCREEN) - tdspeed_init(); + if (global_settings.timestretch_enabled) + tdspeed_init(); #endif /* CONFIG_CODEC == SWCODEC */ theme_init_buffer(); diff --git a/apps/metadata.c b/apps/metadata.c index cbb5b42..4649324 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -20,12 +20,10 @@ ****************************************************************************/ #include #include -#include #include "string-extra.h" #include "debug.h" #include "logf.h" -#include "settings.h" #include "cuesheet.h" #include "metadata.h" @@ -552,9 +550,9 @@ void fill_metadata_from_path(struct mp3entry *id3, const char *trackname) enum { AUTORESUMABLE_UNKNOWN = 0, AUTORESUMABLE_TRUE, AUTORESUMABLE_FALSE }; -bool autoresumable(struct mp3entry *id3) +bool autoresumable(struct mp3entry *id3, const char *autoresume_paths) { - char *endp, *path; + const char *endp, *path; size_t len; bool is_resumable; @@ -565,7 +563,7 @@ bool autoresumable(struct mp3entry *id3) if (id3->path) { - for (path = global_settings.autoresume_paths; + for (path = autoresume_paths; *path; /* search terms left? */ path++) { diff --git a/apps/metadata.h b/apps/metadata.h index a191e43..e5cbf37 100644 --- a/apps/metadata.h +++ b/apps/metadata.h @@ -324,7 +324,7 @@ bool format_buffers_with_offset(int afmt); #endif #ifdef HAVE_TAGCACHE -bool autoresumable(struct mp3entry *id3); +bool autoresumable(struct mp3entry *id3, const char *autoresume_paths); #endif #endif diff --git a/apps/metadata/adx.c b/apps/metadata/adx.c index 4e18254..e427f66 100644 --- a/apps/metadata/adx.c +++ b/apps/metadata/adx.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/aiff.c b/apps/metadata/aiff.c index 654f37c..52582d7 100644 --- a/apps/metadata/aiff.c +++ b/apps/metadata/aiff.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/ape.c b/apps/metadata/ape.c index 44fc69a..4ddb9b0 100644 --- a/apps/metadata/ape.c +++ b/apps/metadata/ape.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/asap.c b/apps/metadata/asap.c index 9e7f227..27c848b 100644 --- a/apps/metadata/asap.c +++ b/apps/metadata/asap.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/asf.c b/apps/metadata/asf.c index 56d5c87..a758852 100644 --- a/apps/metadata/asf.c +++ b/apps/metadata/asf.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include "metadata.h" diff --git a/apps/metadata/flac.c b/apps/metadata/flac.c index 2993717..33a6bf3 100644 --- a/apps/metadata/flac.c +++ b/apps/metadata/flac.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/metadata_common.c b/apps/metadata/metadata_common.c index e620514..f3602d7 100644 --- a/apps/metadata/metadata_common.c +++ b/apps/metadata/metadata_common.c @@ -29,7 +29,6 @@ #include "metadata_common.h" #include "metadata_parsers.h" #include "replaygain.h" -#include "misc.h" /* Read a string from the file. Read up to size bytes, or, if eos != -1, * until the eos character is found (eos is not stored in buf, unless it is @@ -194,7 +193,10 @@ uint32_t get_itunes_int32(char* value, int count) while (count-- > 0) { - value = skip_whitespace(value); + while (isspace(*value)) + { + value++; + } while (*value && !isspace(*value)) { @@ -202,7 +204,10 @@ uint32_t get_itunes_int32(char* value, int count) } } - value = skip_whitespace(value); + while (isspace(*value)) + { + value++; + } while (*value && ((c = strchr(hexdigits, toupper(*value))) != NULL)) { diff --git a/apps/metadata/monkeys.c b/apps/metadata/monkeys.c index 4aff141..c049d26 100644 --- a/apps/metadata/monkeys.c +++ b/apps/metadata/monkeys.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/mp4.c b/apps/metadata/mp4.c index 4feb56c..025ffd1 100644 --- a/apps/metadata/mp4.c +++ b/apps/metadata/mp4.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/nsf.c b/apps/metadata/nsf.c index 29fd847..b9c8266 100644 --- a/apps/metadata/nsf.c +++ b/apps/metadata/nsf.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/ogg.c b/apps/metadata/ogg.c index 3a3cb29..2657fe4 100644 --- a/apps/metadata/ogg.c +++ b/apps/metadata/ogg.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/rm.c b/apps/metadata/rm.c index 27f541c..7ffd1aa 100644 --- a/apps/metadata/rm.c +++ b/apps/metadata/rm.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include diff --git a/apps/metadata/sid.c b/apps/metadata/sid.c index 50b879b..51ba380 100644 --- a/apps/metadata/sid.c +++ b/apps/metadata/sid.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/spc.c b/apps/metadata/spc.c index 1c02062..50ea751 100644 --- a/apps/metadata/spc.c +++ b/apps/metadata/spc.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/tta.c b/apps/metadata/tta.c index 1d3d95f..8ef3754 100644 --- a/apps/metadata/tta.c +++ b/apps/metadata/tta.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/vorbis.c b/apps/metadata/vorbis.c index f6d3af1..60b1523 100644 --- a/apps/metadata/vorbis.c +++ b/apps/metadata/vorbis.c @@ -21,14 +21,12 @@ #include #include #include -#include #include #include "system.h" #include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" -#include "structec.h" /* Define LOGF_ENABLE to enable logf output in this file */ /*#define LOGF_ENABLE*/ diff --git a/apps/metadata/vox.c b/apps/metadata/vox.c index f6bc849..64ccfea 100644 --- a/apps/metadata/vox.c +++ b/apps/metadata/vox.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/metadata/wavpack.c b/apps/metadata/wavpack.c index f2811df..bfb92b7 100644 --- a/apps/metadata/wavpack.c +++ b/apps/metadata/wavpack.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "system.h" diff --git a/apps/playback.c b/apps/playback.c index 2775e8a..29f3db8 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -1291,7 +1291,8 @@ static bool audio_start_codec(bool auto_skip) break; default: /* Not "never resume" - pass resume filter? */ - resume = autoresumable(cur_id3); + resume = autoresumable(cur_id3, + global_settings.autoresume_paths); } } diff --git a/apps/plugins/test_codec.c b/apps/plugins/test_codec.c index 5c98201..9309f22 100644 --- a/apps/plugins/test_codec.c +++ b/apps/plugins/test_codec.c @@ -509,6 +509,12 @@ static enum codec_command_action get_command(intptr_t *param) (void)param; } +/* Some codecs call this to determine whether they should loop. */ +static bool should_loop(void) +{ + return false; +} + static void set_offset(size_t value) { ci.id3->offset = value; @@ -565,6 +571,7 @@ static void init_ci(void) ci.set_offset = set_offset; ci.configure = configure; ci.get_command = get_command; + ci.should_loop = should_loop; /* --- "Core" functions --- */ @@ -582,7 +589,6 @@ static void init_ci(void) ci.memmove = rb->memmove; ci.memcmp = rb->memcmp; ci.memchr = rb->memchr; - ci.strcasestr = rb->strcasestr; #if defined(DEBUG) || defined(SIMULATOR) ci.debugf = rb->debugf; #endif @@ -591,7 +597,6 @@ static void init_ci(void) #endif ci.qsort = rb->qsort; - ci.global_settings = rb->global_settings; #ifdef RB_PROFILE ci.profile_thread = rb->profile_thread; diff --git a/apps/screen_access.c b/apps/screen_access.c index b83e842..1b466ea 100644 --- a/apps/screen_access.c +++ b/apps/screen_access.c @@ -33,6 +33,7 @@ #include "backlight.h" #include "screen_access.h" #include "backdrop.h" +#include "statusbar.h" /* some helper functions to calculate metrics on the fly */ static int screen_helper_getcharwidth(void) diff --git a/apps/settings.h b/apps/settings.h index 05965b3..9678bef 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -27,8 +27,9 @@ #include "inttypes.h" #include "config.h" #include "audiohw.h" /* for the AUDIOHW_* defines */ -#include "statusbar.h" /* for the statusbar values */ +#ifdef HAVE_QUICKSCREEN #include "quickscreen.h" +#endif #include "button.h" #if CONFIG_CODEC == SWCODEC #include "audio.h" diff --git a/apps/tdspeed.c b/apps/tdspeed.c index 61f47f9..04f1210 100644 --- a/apps/tdspeed.c +++ b/apps/tdspeed.c @@ -28,7 +28,6 @@ #include "buffer.h" #include "system.h" #include "tdspeed.h" -#include "settings.h" #define assert(cond) @@ -57,18 +56,15 @@ static int32_t *outbuf[2] = { NULL, NULL }; void tdspeed_init() { - if (global_settings.timestretch_enabled) - { - /* Allocate buffers */ - if (overlap_buffer[0] == NULL) - overlap_buffer[0] = (int32_t *) buffer_alloc(FIXED_BUFSIZE * sizeof(int32_t)); - if (overlap_buffer[1] == NULL) - overlap_buffer[1] = (int32_t *) buffer_alloc(FIXED_BUFSIZE * sizeof(int32_t)); - if (outbuf[0] == NULL) - outbuf[0] = (int32_t *) buffer_alloc(TDSPEED_OUTBUFSIZE * sizeof(int32_t)); - if (outbuf[1] == NULL) - outbuf[1] = (int32_t *) buffer_alloc(TDSPEED_OUTBUFSIZE * sizeof(int32_t)); - } + /* Allocate buffers */ + if (overlap_buffer[0] == NULL) + overlap_buffer[0] = (int32_t *) buffer_alloc(FIXED_BUFSIZE * sizeof(int32_t)); + if (overlap_buffer[1] == NULL) + overlap_buffer[1] = (int32_t *) buffer_alloc(FIXED_BUFSIZE * sizeof(int32_t)); + if (outbuf[0] == NULL) + outbuf[0] = (int32_t *) buffer_alloc(TDSPEED_OUTBUFSIZE * sizeof(int32_t)); + if (outbuf[1] == NULL) + outbuf[1] = (int32_t *) buffer_alloc(TDSPEED_OUTBUFSIZE * sizeof(int32_t)); } diff --git a/warble/Makefile b/warble/Makefile new file mode 100644 index 0000000..472067d --- /dev/null +++ b/warble/Makefile @@ -0,0 +1,63 @@ +default: all + +ROOTDIR = $(shell readlink -e ..) +BUILDDIR = $(shell pwd)/build +APPSDIR = $(ROOTDIR)/apps +TOOLSDIR = $(ROOTDIR)/tools +CODECDIR = $(BUILDDIR)/apps/codecs +DEPFILE = $(BUILDDIR)/make.dep +APP_TYPE = sdl-app + +INCLUDES = -I$(shell pwd) +INCLUDES += -I$(APPSDIR) -I$(APPSDIR)/codecs -I$(APPSDIR)/codecs/lib -I$(APPSDIR)/gui -I$(APPSDIR)/metadata -I$(APPSDIR)/recorder +INCLUDES += -I$(ROOTDIR)/firmware/export -I$(ROOTDIR)/firmware/include -I$(ROOTDIR)/firmware/target/hosted/sdl + +CFLAGS = -D__PCTOOL__ -DSIMULATOR=1 -D"IBSS_ATTR=" -DROCKBOX_LITTLE_ENDIAN=1 -D"ICODE_ATTR=" -D"ICONST_ATTR=" -DROCKBOX $(INCLUDES) +CFLAGS += -O0 -ggdb -DDEBUG -DDEBUGF=debugf -DLOGF_ENABLE +PPCFLAGS = $(CFLAGS) + +SHARED_CFLAGS = -fPIC -fvisibility=hidden +SHARED_LDFLAG = -shared + +WARBLE_OBJS = $(BUILDDIR)/warble.o +WARBLE_CFLAGS = '-DCODECDIR="$(CODECDIR)"' $(shell sdl-config --cflags) +WARBLE_LDFLAGS = -lm -ldl $(shell sdl-config --libs) + +include $(ROOTDIR)/tools/functions.make +include $(APPSDIR)/codecs/codecs.make +CODECFLAGS += -ffreestanding -nostdlib + +SRC = $(ROOTDIR)/apps/metadata.c $(ROOTDIR)/apps/replaygain.c $(ROOTDIR)/firmware/common/strlcpy.c $(ROOTDIR)/firmware/common/unicode.c $(ROOTDIR)/firmware/common/structec.c $(ROOTDIR)/apps/mp3data.c $(ROOTDIR)/apps/fixedpoint.c $(ROOTDIR)/uisimulator/common/io.c +SRC += $(ROOTDIR)/apps/dsp.c $(ROOTDIR)/apps/eq.c $(ROOTDIR)/apps/tdspeed.c +SRC += $(patsubst %,$(ROOTDIR)/apps/metadata/%.c,a52 adx aiff ape asap asf au flac id3tags metadata_common mod monkeys mp3 mp4 mpc nsf ogg oma rm sid smaf spc tta vorbis vox wave wavpack) + +OBJ := $(SRC:.c=.o) +OBJ := $(OBJ:.S=.o) +OBJ := $(subst $(ROOTDIR),$(BUILDDIR),$(OBJ)) + +all: $(DEPFILE) warble $(CODECS) + +dep $(DEPFILE): + $(SILENT)mkdir -p $(dir $(DEPFILE)) + $(call PRINTS,Generating dependencies) + $(call mkdepfile,$(DEPFILE)_,$(SRC)) + $(call mkdepfile,$(DEPFILE)_,$(OTHER_SRC)) + $(call mkdepfile,$(DEPFILE)_,$(ASMDEFS_SRC)) + @mv $(DEPFILE)_ $(DEPFILE) + +-include $(DEPFILE) + +warble: $(WARBLE_OBJS) $(OBJ) + $(call PRINTS,LD $@)$(CC) $(LDFLAGS) $^ -o $@ $(WARBLE_LDFLAGS) + +$(BUILDDIR)/%.o: %.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $<)$(CC) $(CFLAGS) -c $< -o $@ $(WARBLE_CFLAGS) + +$(BUILDDIR)/%.o: $(ROOTDIR)/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $<)$(CC) $(CFLAGS) -c $< -o $@ + +clean: + $(SILENT)echo Cleaning build directory + $(SILENT)rm -rf warble $(BUILDDIR)/* diff --git a/warble/autoconf.h b/warble/autoconf.h new file mode 100644 index 0000000..d79a720 --- /dev/null +++ b/warble/autoconf.h @@ -0,0 +1,16 @@ +#ifndef __BUILD_AUTOCONF_H +#define __BUILD_AUTOCONF_H + +#define CONFIG_CODEC SWCODEC +#define TARGET_ID 73 /* sdlapp */ +#define MEMORYSIZE 64 +#define ROCKBOX_LITTLE_ENDIAN 1 +#define HAVE_PITCHSCREEN +#define HAVE_SW_TONE_CONTROLS +#define HAVE_SW_VOLUME_CONTROL +#define VOLUME_MIN -100 +#define VOLUME_MAX 100 +#define SW_VOLUME_MIN -100 +#define SW_VOLUME_MAX 100 + +#endif /* __BUILD_AUTOCONF_H */ diff --git a/warble/warble.c b/warble/warble.c new file mode 100644 index 0000000..08a8982 --- /dev/null +++ b/warble/warble.c @@ -0,0 +1,824 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2011 Sean Bartell + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#define _BSD_SOURCE /* htole64 from endian.h */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "buffering.h" /* TYPE_PACKET_AUDIO */ +#include "codecs.h" +#include "debug.h" +#include "dsp.h" +#include "metadata.h" +#include "settings.h" +#include "sound.h" + +/***************** EXPORTED *****************/ + +struct user_settings global_settings; +volatile long current_tick = 0; + +void yield() +{ +} + +void *buffer_alloc(size_t size) +{ + return malloc(size); +} + +void mutex_init(struct mutex *m) +{ +} + +void mutex_lock(struct mutex *m) +{ +} + +void mutex_unlock(struct mutex *m) +{ +} + +/***************** INTERNAL *****************/ + +static enum { MODE_PLAY, MODE_WRITE } mode; +static bool use_dsp = true; +static bool enable_loop = false; +static bool verbose = false; +static const char *config = ""; + +static int input_fd; +static enum codec_command_action codec_action; +static intptr_t codec_action_param = 0; +static unsigned long num_output_samples = 0; +static struct codec_api ci; + +static struct { + intptr_t freq; + intptr_t stereo_mode; + intptr_t depth; + int channels; +} format; + +/***** MODE_WRITE *****/ + +#define WAVE_HEADER_SIZE 0x2e +#define WAVE_FORMAT_PCM 1 +#define WAVE_FORMAT_IEEE_FLOAT 3 +static int output_fd; +static bool write_raw = false; +static bool write_header_written = false; + +static void write_init(const char *output_fn) +{ + mode = MODE_WRITE; + if (!strcmp(output_fn, "-")) { + output_fd = STDOUT_FILENO; + } else { + output_fd = creat(output_fn, 0666); + if (output_fd == -1) { + perror(output_fn); + exit(1); + } + } +} + +static void set_le16(char *buf, uint16_t val) +{ + buf[0] = val; + buf[1] = val >> 8; +} + +static void set_le32(char *buf, uint32_t val) +{ + buf[0] = val; + buf[1] = val >> 8; + buf[2] = val >> 16; + buf[3] = val >> 24; +} + +static void write_wav_header() +{ + int channels, sample_size, freq, type; + if (use_dsp) { + channels = 2; + sample_size = 16; + freq = NATIVE_FREQUENCY; + type = WAVE_FORMAT_PCM; + } else { + channels = format.channels; + sample_size = 64; + freq = format.freq; + type = WAVE_FORMAT_IEEE_FLOAT; + } + + /* The size fields are normally overwritten by write_quit(). If that fails, + * this fake size ensures the file can still be played. */ + off_t total_size = 0x7fffff00 + WAVE_HEADER_SIZE; + char header[WAVE_HEADER_SIZE] = {"RIFF____WAVEfmt \x12\0\0\0" + "________________\0\0data____"}; + set_le32(header + 0x04, total_size - 8); + set_le16(header + 0x14, type); + set_le16(header + 0x16, channels); + set_le32(header + 0x18, freq); + set_le32(header + 0x1c, freq * channels * sample_size / 8); + set_le16(header + 0x20, channels * sample_size / 8); + set_le16(header + 0x22, sample_size); + set_le32(header + 0x2a, total_size - WAVE_HEADER_SIZE); + write(output_fd, header, sizeof(header)); + write_header_written = true; +} + +static void write_quit() +{ + if (!write_raw) { + /* Write the correct size fields in the header. If lseek fails (e.g. + * for a pipe) nothing is written. */ + off_t total_size = lseek(output_fd, 0, SEEK_CUR); + if (total_size != (off_t)-1) { + char buf[4]; + set_le32(buf, total_size - 8); + lseek(output_fd, 4, SEEK_SET); + write(output_fd, buf, 4); + set_le32(buf, total_size - WAVE_HEADER_SIZE); + lseek(output_fd, 0x2a, SEEK_SET); + write(output_fd, buf, 4); + } + } + if (output_fd != STDOUT_FILENO) + close(output_fd); +} + +static uint64_t make_float64(int32_t sample, int shift) +{ + /* TODO: be more portable */ + double val = ldexp(sample, -shift); + return *(uint64_t*)&val; +} + +static void write_pcm(int16_t *pcm, int count) +{ + if (!write_header_written) + write_wav_header(); + int i; + for (i = 0; i < 2 * count; i++) + pcm[i] = htole16(pcm[i]); + write(output_fd, pcm, 4 * count); +} + +static void write_pcm_raw(int32_t *pcm, int count) +{ + if (write_raw) { + write(output_fd, pcm, count * sizeof(*pcm)); + } else { + if (!write_header_written) + write_wav_header(); + int i; + uint64_t buf[count]; + + for (i = 0; i < count; i++) + buf[i] = htole64(make_float64(pcm[i], format.depth)); + write(output_fd, buf, count * sizeof(*buf)); + } +} + +/***** MODE_PLAY *****/ + +/* MODE_PLAY uses a double buffer: one half is read by the playback thread and + * the other half is written to by the main thread. When a thread is done with + * its current half, it waits for the other thread and then switches. The main + * advantage of this method is its simplicity; the main disadvantage is that it + * has long latency. ALSA buffer underruns still occur sometimes, but this is + * SDL's fault. */ + +#define PLAYBACK_BUFFER_SIZE 0x10000 +static bool playback_running = false; +static char playback_buffer[2][PLAYBACK_BUFFER_SIZE]; +static int playback_play_ind, playback_decode_ind; +static int playback_play_pos, playback_decode_pos; +static SDL_sem *playback_play_sema, *playback_decode_sema; + +static void playback_init() +{ + mode = MODE_PLAY; + if (SDL_Init(SDL_INIT_AUDIO)) { + fprintf(stderr, "error: Can't initialize SDL: %s\n", SDL_GetError()); + exit(1); + } + playback_play_ind = 1; + playback_play_pos = PLAYBACK_BUFFER_SIZE; + playback_decode_ind = 0; + playback_decode_pos = 0; + playback_play_sema = SDL_CreateSemaphore(0); + playback_decode_sema = SDL_CreateSemaphore(0); +} + +static void playback_callback(void *userdata, Uint8 *stream, int len) +{ + while (len > 0) { + if (!playback_running && playback_play_ind == playback_decode_ind + && playback_play_pos >= playback_decode_pos) { + /* end of data */ + memset(stream, 0, len); + SDL_SemPost(playback_play_sema); + return; + } + if (playback_play_pos >= PLAYBACK_BUFFER_SIZE) { + SDL_SemPost(playback_play_sema); + SDL_SemWait(playback_decode_sema); + playback_play_ind = !playback_play_ind; + playback_play_pos = 0; + } + char *play_buffer = playback_buffer[playback_play_ind]; + int copy_len = MIN(len, PLAYBACK_BUFFER_SIZE - playback_play_pos); + memcpy(stream, play_buffer + playback_play_pos, copy_len); + len -= copy_len; + stream += copy_len; + playback_play_pos += copy_len; + } +} + +static void playback_start() +{ + playback_running = true; + SDL_AudioSpec spec = {0}; + spec.freq = NATIVE_FREQUENCY; + spec.format = AUDIO_S16SYS; + spec.channels = 2; + spec.samples = 0x400; + spec.callback = playback_callback; + spec.userdata = NULL; + if (SDL_OpenAudio(&spec, NULL)) { + fprintf(stderr, "error: Can't open SDL audio: %s\n", SDL_GetError()); + exit(1); + } + SDL_PauseAudio(0); +} + +static void playback_quit() +{ + if (!playback_running) + playback_start(); + memset(playback_buffer[playback_decode_ind] + playback_decode_pos, 0, + PLAYBACK_BUFFER_SIZE - playback_decode_pos); + playback_running = false; + SDL_SemPost(playback_decode_sema); + SDL_SemWait(playback_play_sema); + SDL_SemWait(playback_play_sema); + SDL_Quit(); +} + +static void playback_pcm(int16_t *pcm, int count) +{ + const char *stream = (const char *)pcm; + count *= 4; + + while (count > 0) { + if (playback_decode_pos >= PLAYBACK_BUFFER_SIZE) { + if (!playback_running) + playback_start(); + SDL_SemPost(playback_decode_sema); + SDL_SemWait(playback_play_sema); + playback_decode_ind = !playback_decode_ind; + playback_decode_pos = 0; + } + char *decode_buffer = playback_buffer[playback_decode_ind]; + int copy_len = MIN(count, PLAYBACK_BUFFER_SIZE - playback_decode_pos); + memcpy(decode_buffer + playback_decode_pos, stream, copy_len); + stream += copy_len; + count -= copy_len; + playback_decode_pos += copy_len; + } +} + +/***** ALL MODES *****/ + +static void perform_config() +{ + while (config) { + const char *name = config; + const char *eq = strchr(config, '='); + if (!eq) + break; + const char *val = eq + 1; + const char *end = val + strcspn(val, ": \t\n"); + + if (!strncmp(name, "wait=", 5)) { + if (atoi(val) > num_output_samples) + return; + } else if (!strncmp(name, "dither=", 7)) { + dsp_dither_enable(atoi(val) ? true : false); + } else if (!strncmp(name, "halt=", 5)) { + if (atoi(val)) + codec_action = CODEC_ACTION_HALT; + } else if (!strncmp(name, "loop=", 5)) { + enable_loop = atoi(val) != 0; + } else if (!strncmp(name, "offset=", 7)) { + ci.id3->offset = atoi(val); + } else if (!strncmp(name, "rate=", 5)) { + sound_set_pitch(atof(val) * PITCH_SPEED_100); + } else if (!strncmp(name, "seek=", 5)) { + codec_action = CODEC_ACTION_SEEK_TIME; + codec_action_param = atoi(val); + } else if (!strncmp(name, "tempo=", 6)) { + dsp_set_timestretch(atof(val) * PITCH_SPEED_100); + } else if (!strncmp(name, "vol=", 4)) { + global_settings.volume = atoi(val); + dsp_callback(DSP_CALLBACK_SET_SW_VOLUME, 0); + } else { + fprintf(stderr, "error: unrecognized config \"%.*s\"\n", eq - name, name); + exit(1); + } + + if (*end) + config = end + 1; + else + config = NULL; + } +} + +static void *ci_codec_get_buffer(size_t *size) +{ + static char buffer[64 * 1024 * 1024]; + char *ptr = buffer; + *size = sizeof(buffer); + ALIGN_BUFFER(ptr, *size, CACHEALIGN_SIZE); + return ptr; +} + +static void ci_pcmbuf_insert(const void *ch1, const void *ch2, int count) +{ + num_output_samples += count; + + if (use_dsp) { + const char *src[2] = {ch1, ch2}; + while (count > 0) { + int out_count = dsp_output_count(ci.dsp, count); + int in_count = MIN(dsp_input_count(ci.dsp, out_count), count); + int16_t buf[2 * out_count]; + out_count = dsp_process(ci.dsp, (char *)buf, src, in_count); + if (mode == MODE_WRITE) + write_pcm(buf, out_count); + else if (mode == MODE_PLAY) + playback_pcm(buf, out_count); + count -= in_count; + } + } else { + /* Convert to 32-bit interleaved. */ + count *= format.channels; + int i; + int32_t buf[count]; + if (format.depth > 16) { + if (format.stereo_mode == STEREO_NONINTERLEAVED) { + for (i = 0; i < count; i += 2) { + buf[i+0] = ((int32_t*)ch1)[i/2]; + buf[i+1] = ((int32_t*)ch2)[i/2]; + } + } else { + memcpy(buf, ch1, sizeof(buf)); + } + } else { + if (format.stereo_mode == STEREO_NONINTERLEAVED) { + for (i = 0; i < count; i += 2) { + buf[i+0] = ((int16_t*)ch1)[i/2]; + buf[i+1] = ((int16_t*)ch2)[i/2]; + } + } else { + for (i = 0; i < count; i++) { + buf[i] = ((int16_t*)ch1)[i]; + } + } + } + + if (mode == MODE_WRITE) + write_pcm_raw(buf, count); + } + + perform_config(); +} + +static void ci_set_elapsed(unsigned long value) +{ + //debugf("Time elapsed: %lu\n", value); +} + +static char *input_buffer = 0; + +/* + * Read part of the input file into a provided buffer. + * + * The entire size requested will be provided except at the end of the file. + * The current file position will be moved, just like with advance_buffer, but + * the offset is not updated. This invalidates buffers returned by + * request_buffer. + */ +static size_t ci_read_filebuf(void *ptr, size_t size) +{ + free(input_buffer); + input_buffer = NULL; + + ssize_t actual = read(input_fd, ptr, size); + if (actual < 0) + actual = 0; + ci.curpos += actual; + return actual; +} + +/* + * Request a buffer containing part of the input file. + * + * The size provided will be the requested size, or the remaining size of the + * file, whichever is smaller. Packet audio has an additional maximum of 32 + * KiB. The returned buffer remains valid until the next time read_filebuf, + * request_buffer, advance_buffer, or seek_buffer is called. + */ +static void *ci_request_buffer(size_t *realsize, size_t reqsize) +{ + free(input_buffer); + if (get_audio_base_data_type(ci.id3->codectype) == TYPE_PACKET_AUDIO) + reqsize = MIN(reqsize, 32 * 1024); + input_buffer = malloc(reqsize); + *realsize = read(input_fd, input_buffer, reqsize); + if (*realsize < 0) + *realsize = 0; + lseek(input_fd, -*realsize, SEEK_CUR); + return input_buffer; +} + +/* + * Advance the current position in the input file. + * + * This automatically updates the current offset. This invalidates buffers + * returned by request_buffer. + */ +static void ci_advance_buffer(size_t amount) +{ + free(input_buffer); + input_buffer = NULL; + + lseek(input_fd, amount, SEEK_CUR); + ci.curpos += amount; + ci.id3->offset = ci.curpos; +} + +/* + * Seek to a position in the input file. + * + * This invalidates buffers returned by request_buffer. + */ +static bool ci_seek_buffer(size_t newpos) +{ + free(input_buffer); + input_buffer = NULL; + + off_t actual = lseek(input_fd, newpos, SEEK_SET); + if (actual >= 0) + ci.curpos = actual; + return actual != -1; +} + +static void ci_seek_complete() +{ +} + +static void ci_set_offset(size_t value) +{ + ci.id3->offset = value; +} + +static void ci_configure(int setting, intptr_t value) +{ + if (use_dsp) { + dsp_configure(ci.dsp, setting, value); + } else { + if (setting == DSP_SET_FREQUENCY + || setting == DSP_SWITCH_FREQUENCY) + format.freq = value; + else if (setting == DSP_SET_SAMPLE_DEPTH) + format.depth = value; + else if (setting == DSP_SET_STEREO_MODE) { + format.stereo_mode = value; + format.channels = (value == STEREO_MONO) ? 1 : 2; + } + } +} + +static enum codec_command_action ci_get_command(intptr_t *param) +{ + enum codec_command_action ret = codec_action; + *param = codec_action_param; + codec_action = CODEC_ACTION_NULL; + return ret; +} + +static bool ci_should_loop() +{ + return enable_loop; +} + +static unsigned ci_sleep(unsigned ticks) +{ + return 0; +} + +static void ci_cpucache_flush() +{ +} + +static void ci_cpucache_invalidate() +{ +} + +static struct codec_api ci = { + + 0, /* filesize */ + 0, /* curpos */ + NULL, /* id3 */ + -1, /* audio_hid */ + NULL, /* struct dsp_config *dsp */ + ci_codec_get_buffer, /* codec_get_buffer */ + ci_pcmbuf_insert, /* pcmbuf_insert */ + ci_set_elapsed, /* set_elapsed */ + ci_read_filebuf, /* read_filebuf */ + ci_request_buffer, /* request_buffer */ + ci_advance_buffer, /* advance_buffer */ + ci_seek_buffer, /* seek_buffer */ + ci_seek_complete, /* seek_complete */ + ci_set_offset, /* set_offset */ + ci_configure, /* configure */ + ci_get_command, /* get_command */ + ci_should_loop, /* should_loop */ + + ci_sleep, + yield, + +#if NUM_CORES > 1 + ci_create_thread, + ci_thread_thaw, + ci_thread_wait, + ci_semaphore_init, + ci_semaphore_wait, + ci_semaphore_release, +#endif + + ci_cpucache_flush, + ci_cpucache_invalidate, + + /* strings and memory */ + strcpy, + strlen, + strcmp, + strcat, + memset, + memcpy, + memmove, + memcmp, + memchr, +#if defined(DEBUG) || defined(SIMULATOR) + debugf, +#endif +#ifdef ROCKBOX_HAS_LOGF + debugf, /* logf */ +#endif + + qsort, + +#ifdef HAVE_RECORDING + ci_enc_get_inputs, + ci_enc_set_parameters, + ci_enc_get_chunk, + ci_enc_finish_chunk, + ci_enc_get_pcm_data, + ci_enc_unget_pcm_data, + + /* file */ + open, + close, + read, + lseek, + write, + ci_round_value_to_list32, + +#endif /* HAVE_RECORDING */ +}; + +static void print_mp3entry(const struct mp3entry *id3, FILE *f) +{ + fprintf(f, "Path: %s\n", id3->path); + if (id3->title) fprintf(f, "Title: %s\n", id3->title); + if (id3->artist) fprintf(f, "Artist: %s\n", id3->artist); + if (id3->album) fprintf(f, "Album: %s\n", id3->album); + if (id3->genre_string) fprintf(f, "Genre: %s\n", id3->genre_string); + if (id3->disc_string || id3->discnum) fprintf(f, "Disc: %s (%d)\n", id3->disc_string, id3->discnum); + if (id3->track_string || id3->tracknum) fprintf(f, "Track: %s (%d)\n", id3->track_string, id3->tracknum); + if (id3->year_string || id3->year) fprintf(f, "Year: %s (%d)\n", id3->year_string, id3->year); + if (id3->composer) fprintf(f, "Composer: %s\n", id3->composer); + if (id3->comment) fprintf(f, "Comment: %s\n", id3->comment); + if (id3->albumartist) fprintf(f, "Album artist: %s\n", id3->albumartist); + if (id3->grouping) fprintf(f, "Grouping: %s\n", id3->grouping); + if (id3->layer) fprintf(f, "Layer: %d\n", id3->layer); + if (id3->id3version) fprintf(f, "ID3 version: %u\n", (int)id3->id3version); + fprintf(f, "Codec: %s\n", audio_formats[id3->codectype].label); + fprintf(f, "Bitrate: %d kb/s\n", id3->bitrate); + fprintf(f, "Frequency: %lu Hz\n", id3->frequency); + if (id3->id3v2len) fprintf(f, "ID3v2 length: %lu\n", id3->id3v2len); + if (id3->id3v1len) fprintf(f, "ID3v1 length: %lu\n", id3->id3v1len); + if (id3->first_frame_offset) fprintf(f, "First frame offset: %lu\n", id3->first_frame_offset); + fprintf(f, "File size without headers: %lu\n", id3->filesize); + fprintf(f, "Song length: %lu ms\n", id3->length); + if (id3->lead_trim || id3->tail_trim) fprintf(f, "Trim: %d/%d\n", id3->lead_trim, id3->tail_trim); + if (id3->samples) fprintf(f, "Number of samples: %lu\n", id3->samples); + if (id3->frame_count) fprintf(f, "Number of frames: %lu\n", id3->frame_count); + if (id3->bytesperframe) fprintf(f, "Bytes per frame: %lu\n", id3->bytesperframe); + if (id3->vbr) fprintf(f, "VBR: true\n"); + if (id3->has_toc) fprintf(f, "Has TOC: true\n"); + if (id3->channels) fprintf(f, "Number of channels: %u\n", id3->channels); + if (id3->extradata_size) fprintf(f, "Size of extra data: %u\n", id3->extradata_size); + if (id3->needs_upsampling_correction) fprintf(f, "Needs upsampling correction: true\n"); + /* TODO: replaygain; albumart; cuesheet */ + if (id3->mb_track_id) fprintf(f, "Musicbrainz track ID: %s\n", id3->mb_track_id); +} + +static void decode_file(const char *input_fn) +{ + /* Set up global settings */ + memset(&global_settings, 0, sizeof(global_settings)); + global_settings.timestretch_enabled = true; + dsp_timestretch_enable(true); + tdspeed_init(); + + /* Open file */ + if (!strcmp(input_fn, "-")) { + input_fd = STDIN_FILENO; + } else { + input_fd = open(input_fn, O_RDONLY); + if (input_fd == -1) { + perror(input_fn); + exit(1); + } + } + + /* Set up ci */ + struct mp3entry id3; + if (!get_metadata(&id3, input_fd, input_fn)) { + fprintf(stderr, "error: metadata parsing failed\n"); + exit(1); + } + print_mp3entry(&id3, stderr); + ci.filesize = filesize(input_fd); + ci.id3 = &id3; + if (use_dsp) { + ci.dsp = (struct dsp_config *)dsp_configure(NULL, DSP_MYDSP, CODEC_IDX_AUDIO); + dsp_configure(ci.dsp, DSP_RESET, 0); + dsp_dither_enable(false); + } + perform_config(); + + /* Load codec */ + char str[MAX_PATH]; + snprintf(str, sizeof(str), CODECDIR"/%s.codec", audio_formats[id3.codectype].codec_root_fn); + debugf("Loading %s\n", str); + void *dlcodec = dlopen(str, RTLD_NOW); + if (!dlcodec) { + fprintf(stderr, "error: dlopen failed: %s\n", dlerror()); + exit(1); + } + struct codec_header *c_hdr = NULL; + c_hdr = dlsym(dlcodec, "__header"); + if (c_hdr->lc_hdr.magic != CODEC_MAGIC) { + fprintf(stderr, "error: %s invalid: incorrect magic\n", str); + exit(1); + } + if (c_hdr->lc_hdr.target_id != TARGET_ID) { + fprintf(stderr, "error: %s invalid: incorrect target id\n", str); + exit(1); + } + if (c_hdr->lc_hdr.api_version != CODEC_API_VERSION) { + fprintf(stderr, "error: %s invalid: incorrect API version\n", str); + exit(1); + } + + /* Run the codec */ + *c_hdr->api = &ci; + if (c_hdr->entry_point(CODEC_LOAD) != CODEC_OK) { + fprintf(stderr, "error: codec returned error from codec_main\n"); + exit(1); + } + if (c_hdr->run_proc() != CODEC_OK) { + fprintf(stderr, "error: codec error\n"); + } + c_hdr->entry_point(CODEC_UNLOAD); + + /* Close */ + dlclose(dlcodec); + if (input_fd != STDIN_FILENO) + close(input_fd); +} + +static void print_help(const char *progname) +{ + fprintf(stderr, "Usage:\n" + " Play: %s [options] INPUTFILE\n" + "Write to WAV: %s [options] INPUTFILE OUTPUTFILE\n" + "\n" + "general options:\n" + " -c a=1:b=2 Configuration (see below)\n" + " -h Show this help\n" + "\n" + "write to WAV options:\n" + " -f Write raw codec output converted to 64-bit float\n" + " -r Write raw 32-bit codec output without WAV header\n" + "\n" + "configuration:\n" + " dither=<0|1> Enable/disable dithering [0]\n" + " halt=<0|1> Stop decoding if 1 [0]\n" + " loop=<0|1> Enable/disable looping [0]\n" + " offset= Start at byte offset within the file [0]\n" + " rate= Multiply rate by [1.0]\n" + " seek= Seek ms into the file\n" + " tempo= Timestretch by [1.0]\n" + " vol= Set volume to dB [0]\n" + " wait= Don't apply remaining configuration until\n" + " total samples have output\n" + "\n" + "examples:\n" + " # Play while looping; stop after 44100 output samples\n" + " %s in.adx -c loop=1:wait=44100:halt=1\n" + " # Lower pitch 1 octave and write to out.wav\n" + " %s in.ogg -c rate=0.5:tempo=2 out.wav\n" + , progname, progname, progname, progname); +} + +int main(int argc, char **argv) +{ + int opt; + while ((opt = getopt(argc, argv, "c:fhr")) != -1) { + switch (opt) { + case 'c': + config = optarg; + break; + case 'f': + use_dsp = false; + break; + case 'r': + use_dsp = false; + write_raw = true; + break; + case 'h': /* fallthrough */ + default: + print_help(argv[0]); + exit(1); + } + } + + if (argc == optind + 2) { + write_init(argv[optind + 1]); + } else if (argc == optind + 1) { + if (!use_dsp) { + fprintf(stderr, "error: -r can't be used for playback\n"); + print_help(argv[0]); + exit(1); + } + playback_init(); + } else { + if (argc > 1) + fprintf(stderr, "error: wrong number of arguments\n"); + print_help(argv[0]); + exit(1); + } + + decode_file(argv[optind]); + + if (mode == MODE_WRITE) + write_quit(); + else if (mode == MODE_PLAY) + playback_quit(); + + return 0; +}