diff --git a/apps/lang/english.lang b/apps/lang/english.lang index b7c829f4b8..4c38fad925 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -3166,3 +3166,15 @@ desc: in set_rating eng: "Rating:" voice "Rating" new: + +id: LANG_CROSSFADE_DURATION +desc: in playback settings +eng: "Crossfade duration" +voice: "Crossfade duration" +new: + +id: LANG_MIX +desc: in playback settings, crossfade option +eng: "Mix" +voice: "Mix" +new: diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c index a8769e3829..691f8d5a19 100644 --- a/apps/pcmbuf.c +++ b/apps/pcmbuf.c @@ -69,6 +69,7 @@ static bool boost_mode; */ enum { CFM_CROSSFADE, + CFM_MIX, CFM_FLUSH }; @@ -221,7 +222,7 @@ bool pcmbuf_is_lowdata(void) return false; } -bool pcmbuf_crossfade_init(void) +bool pcmbuf_crossfade_init(int type) { if (pcmbuf_size - audiobuffer_free < CHUNK_SIZE * 8 || !crossfade_enabled || crossfade_active || crossfade_init) { @@ -230,7 +231,18 @@ bool pcmbuf_crossfade_init(void) } logf("pcmbuf_crossfade_init"); pcmbuf_boost(true); - crossfade_mode = CFM_CROSSFADE; + + switch (type) { + case CROSSFADE_MODE_CROSSFADE: + crossfade_mode = CFM_CROSSFADE; + break; + case CROSSFADE_MODE_MIX: + crossfade_mode = CFM_MIX; + break; + default: + return false; + } + crossfade_init = true; return true; @@ -330,6 +342,7 @@ static void crossfade_start(void) crossfade_pos = audiobuffer_pos; switch (crossfade_mode) { + case CFM_MIX: case CFM_CROSSFADE: crossfade_amount = (bytesleft - (CHUNK_SIZE * 2))/2; crossfade_rem = crossfade_amount; @@ -354,6 +367,15 @@ int crossfade(short *buf, const short *buf2, int length) size = MIN(length, crossfade_rem); switch (crossfade_mode) { + /* Mix two streams. */ + case CFM_MIX: + /* Bias & add & clip. */ + for (i = 0; i < size; i++) { + buf[i] = MIN(MAX(buf[i] + buf2[i], -32768), 32767); + } + break ; + + /* Fade two streams. */ case CFM_CROSSFADE: val1 = (crossfade_rem<<10)/crossfade_amount; val2 = ((crossfade_amount-crossfade_rem)<<10)/crossfade_amount; @@ -363,6 +385,7 @@ int crossfade(short *buf, const short *buf2, int length) } break ; + /* Join two streams. */ case CFM_FLUSH: for (i = 0; i < size; i++) { buf[i] = buf2[i]; diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h index 29217afc9b..6381dbc27e 100644 --- a/apps/pcmbuf.h +++ b/apps/pcmbuf.h @@ -36,7 +36,7 @@ void pcmbuf_set_watermark(int numbytes, void (*callback)(int bytes_left)); void pcmbuf_set_boost_mode(bool state); bool pcmbuf_is_lowdata(void); void pcmbuf_flush_audio(void); -bool pcmbuf_crossfade_init(void); +bool pcmbuf_crossfade_init(int type); void pcmbuf_add_event(void (*event_handler)(void)); unsigned int pcmbuf_get_latency(void); bool pcmbuf_insert_buffer(char *buf, long length); diff --git a/apps/playback.c b/apps/playback.c index 7aaff01e54..f502d8291b 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -1175,7 +1175,8 @@ void audio_update_trackinfo(void) cur_ti->start_pos = 0; ci.taginfo_ready = (bool *)&cur_ti->taginfo_ready; if (pcmbuf_is_crossfade_enabled() && !pcmbuf_is_crossfade_active()) { - pcmbuf_crossfade_init(); + pcmbuf_crossfade_init(new_track ? CROSSFADE_MODE_CROSSFADE + : global_settings.crossfade); codec_track_changed(); } else { pcmbuf_add_event(codec_track_changed); @@ -1393,7 +1394,7 @@ void audio_thread(void) ci.stop_codec = true; ci.reload_codec = false; ci.seek_time = 0; - pcmbuf_crossfade_init(); + pcmbuf_crossfade_init(CROSSFADE_MODE_CROSSFADE); while (codec_loaded) yield(); audio_play_start((int)ev.data); @@ -1771,19 +1772,22 @@ void audio_set_buffer_margin(int setting) set_filebuf_watermark(buffer_margin); } -void audio_set_crossfade_amount(int seconds) +/* Set crossfade & PCM buffer length. */ +void audio_set_crossfade(int type) { long size; bool was_playing = playing; int offset = 0; + int lookup[] = {1, 2, 4, 6, 8, 10, 12, 14}; + int seconds = lookup[global_settings.crossfade_duration]; /* Store the track resume position */ if (playing) offset = cur_ti->id3.offset; - /* Multiply by two to get the real value (0s, 2s, 4s, ...) */ - seconds *= 2; - + if (type == CROSSFADE_MODE_OFF) + seconds = 0; + /* Buffer has to be at least 2s long. */ seconds += 2; logf("buf len: %d", seconds); diff --git a/apps/settings.c b/apps/settings.c index d374f00ff2..efb9cfd9de 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -397,7 +397,7 @@ static const struct bit_entry hd_bits[] = #endif #if CONFIG_HWCODEC == MASNONE - {3, S_O(crossfade), 0, "crossfade", "off,2s,4s,6s,8s,10s,12s,14s"}, + {3, S_O(crossfade_duration), 0, "crossfade duration", "1s,2s,4s,6s,8s,10s,12s,14s"}, #endif #if CONFIG_BACKLIGHT == BL_IRIVER @@ -414,6 +414,10 @@ static const struct bit_entry hd_bits[] = {1, S_O(next_folder), false, "move to next folder", off_on }, {1, S_O(runtimedb), false, "gather runtime data", off_on }, +#if CONFIG_HWCODEC == MASNONE + {2, S_O(crossfade), 0, "crossfade type", "off,crossfade,mix"}, +#endif + /* new stuff to be added at the end */ /* Sum of all bit sizes must not grow beyond 0xB8*8 = 1472 */ @@ -851,7 +855,7 @@ void settings_apply(void) } #if CONFIG_HWCODEC == MASNONE - audio_set_crossfade_amount(global_settings.crossfade); + audio_set_crossfade(global_settings.crossfade); #endif #ifdef HAVE_SPDIF_POWER diff --git a/apps/settings.h b/apps/settings.h index 14c8347d3c..3e3a982b94 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -106,6 +106,10 @@ #define TRIG_DURATION_COUNT 13 extern char *trig_durations[TRIG_DURATION_COUNT]; +#define CROSSFADE_MODE_OFF 0 +#define CROSSFADE_MODE_CROSSFADE 1 +#define CROSSFADE_MODE_MIX 2 + /* These define "virtual pointers", which could either be a literal string, or a mean a string ID if the pointer is in a certain range. This helps to save space for menus and options. */ @@ -151,6 +155,7 @@ struct user_settings #if CONFIG_HWCODEC == MASNONE int crossfade; + int crossfade_duration; #endif int rec_quality; /* 0-7 */ diff --git a/apps/settings_menu.c b/apps/settings_menu.c index df5f4a49ae..5b03a8d22e 100644 --- a/apps/settings_menu.c +++ b/apps/settings_menu.c @@ -1131,6 +1131,22 @@ static bool crossfade(void) { static const struct opt_items names[] = { { STR(LANG_OFF) }, + { STR(LANG_CROSSFADE) }, + { STR(LANG_MIX) }, + }; + bool ret; + + ret = set_option( str(LANG_CROSSFADE), + &global_settings.crossfade, INT, names, 3, NULL); + audio_set_crossfade(global_settings.crossfade); + + return ret; +} + +static bool crossfade_duration(void) +{ + static const struct opt_items names[] = { + { "1s", TALK_ID(1, UNIT_SEC) }, { "2s", TALK_ID(2, UNIT_SEC) }, { "4s", TALK_ID(4, UNIT_SEC) }, { "6s", TALK_ID(6, UNIT_SEC) }, @@ -1140,13 +1156,12 @@ static bool crossfade(void) { "14s", TALK_ID(14, UNIT_SEC) }, }; bool ret; - ret=set_option( str(LANG_CROSSFADE), &global_settings.crossfade, - INT, names, 8, NULL); - audio_set_crossfade_amount(global_settings.crossfade); + ret=set_option( str(LANG_CROSSFADE_DURATION), + &global_settings.crossfade_duration, INT, names, 8, NULL); + audio_set_crossfade(global_settings.crossfade); return ret; } - #endif static bool next_folder(void) @@ -1187,6 +1202,7 @@ static bool playback_settings_menu(void) { ID2P(LANG_FADE_ON_STOP), set_fade_on_stop }, #if CONFIG_HWCODEC == MASNONE { ID2P(LANG_CROSSFADE), crossfade }, + { ID2P(LANG_CROSSFADE_DURATION), crossfade_duration }, #endif #ifdef HAVE_SPDIF_POWER { ID2P(LANG_SPDIF_ENABLE), spdif }, diff --git a/firmware/export/audio.h b/firmware/export/audio.h index c8746c1aac..17de7f077d 100644 --- a/firmware/export/audio.h +++ b/firmware/export/audio.h @@ -71,7 +71,7 @@ struct mp3entry* audio_current_track(void); struct mp3entry* audio_next_track(void); bool audio_has_changed_track(void); void audio_get_debugdata(struct audio_debug *dbgdata); -void audio_set_crossfade_amount(int seconds); +void audio_set_crossfade(int type); void audio_set_buffer_margin(int seconds); unsigned int audio_error(void); void audio_error_clear(void); diff --git a/firmware/pcm_playback.c b/firmware/pcm_playback.c index bafbd8c761..9bba88eb73 100644 --- a/firmware/pcm_playback.c +++ b/firmware/pcm_playback.c @@ -286,7 +286,7 @@ void DMA0(void) /* Stop on error */ if(res & 0x70) { - dma_stop(); + pcm_play_stop(); logf("DMA Error:0x%04x", res); } else @@ -302,7 +302,7 @@ void DMA0(void) else { /* Finished playing */ - dma_stop(); + pcm_play_stop(); logf("DMA No Data:0x%04x", res); } }