Fix FS#12238 - WPS delay on pause introduced by r30097 which was the excuse I wanted anyway to do a better PCM fade on stop/pause implementation. New fade is asynchronous tick-based. Restores skin update points in the WPS that were removed when fading mechanism was changed.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30340 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/gui/wps.c b/apps/gui/wps.c
index 80522d0..48d9e09 100644
--- a/apps/gui/wps.c
+++ b/apps/gui/wps.c
@@ -119,14 +119,28 @@
return skin_buf[screen];
}
+static void update_non_static(void)
+{
+ int i;
+ FOR_NB_SCREENS(i)
+ skin_update(WPS, i, SKIN_REFRESH_NON_STATIC);
+}
+
void pause_action(bool may_fade, bool updatewps)
{
-#if CONFIG_CODEC != SWCODEC
+#if CONFIG_CODEC == SWCODEC
+ /* Do audio first, then update, unless skin were to use its local
+ status in which case, reverse it */
+ audio_pause();
+
+ if (updatewps)
+ update_non_static();
+#else
if (may_fade && global_settings.fade_on_stop)
fade(false, updatewps);
else
-#endif
audio_pause();
+#endif
if (global_settings.pause_rewind) {
long newpos;
@@ -139,18 +153,26 @@
audio_ff_rewind(newpos > 0 ? newpos : 0);
}
- (void)may_fade; (void)updatewps;
+ (void)may_fade;
}
void unpause_action(bool may_fade, bool updatewps)
{
-#if CONFIG_CODEC != SWCODEC
+#if CONFIG_CODEC == SWCODEC
+ /* Do audio first, then update, unless skin were to use its local
+ status in which case, reverse it */
+ audio_resume();
+
+ if (updatewps)
+ update_non_static();
+#else
if (may_fade && global_settings.fade_on_stop)
fade(true, updatewps);
else
-#endif
audio_resume();
- (void)may_fade; (void)updatewps;
+#endif
+
+ (void)may_fade;
}
#if CONFIG_CODEC != SWCODEC
@@ -159,7 +181,7 @@
int fp_global_vol = global_settings.volume << 8;
int fp_min_vol = sound_min(SOUND_VOLUME) << 8;
int fp_step = (fp_global_vol - fp_min_vol) / 10;
- int i;
+
skin_get_global_state()->is_fading = !fade_in;
if (fade_in) {
/* fade in */
@@ -171,10 +193,8 @@
sleep(HZ/10); /* let audio thread run */
audio_resume();
- if (updatewps) {
- FOR_NB_SCREENS(i)
- skin_update(WPS, i, SKIN_REFRESH_NON_STATIC);
- }
+ if (updatewps)
+ update_non_static();
while (fp_volume < fp_global_vol - fp_step) {
fp_volume += fp_step;
@@ -187,10 +207,8 @@
/* fade out */
int fp_volume = fp_global_vol;
- if (updatewps) {
- FOR_NB_SCREENS(i)
- skin_update(WPS, i, SKIN_REFRESH_NON_STATIC);
- }
+ if (updatewps)
+ update_non_static();
while (fp_volume > fp_min_vol + fp_step) {
fp_volume -= fp_step;
@@ -1140,6 +1158,7 @@
fade(false, true);
#else
audio_pause();
+ update_non_static();
#endif
if (bookmark)
bookmark_autobookmark(true);
diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c
index 2e8bc3f..2ba6b6f 100644
--- a/apps/pcmbuf.c
+++ b/apps/pcmbuf.c
@@ -91,6 +91,13 @@
/* Fade effect */
static unsigned int fade_vol = MIX_AMP_UNITY;
+static enum
+{
+ PCM_NOT_FADING = 0,
+ PCM_FADING_IN,
+ PCM_FADING_OUT,
+} fade_state = PCM_NOT_FADING;
+static bool fade_out_complete = false;
/* Voice */
static bool soft_mode = false;
@@ -628,8 +635,9 @@
* operations involved in sending a new chunk to the DMA. */
static void pcmbuf_pcm_callback(unsigned char** start, size_t* size)
{
+ struct chunkdesc *pcmbuf_current = read_chunk;
+
{
- struct chunkdesc *pcmbuf_current = read_chunk;
/* Take the finished chunk out of circulation */
read_chunk = pcmbuf_current->link;
@@ -657,25 +665,28 @@
{
/* Commit last samples at end of playlist */
- if (pcmbuffer_fillpos && !read_chunk)
+ if (pcmbuffer_fillpos && !pcmbuf_current)
{
logf("pcmbuf_pcm_callback: commit last samples");
commit_chunk(false);
}
}
+ /* Stop at this frame */
+ pcmbuf_current = fade_out_complete ? NULL : read_chunk;
+
{
/* Send the new chunk to the DMA */
- if(read_chunk)
+ if(pcmbuf_current)
{
- last_chunksize = read_chunk->size;
+ last_chunksize = pcmbuf_current->size;
pcmbuf_unplayed_bytes -= last_chunksize;
*size = last_chunksize;
- *start = read_chunk->addr;
+ *start = pcmbuf_current->addr;
}
else
{
- /* No more chunks */
+ /* No more chunks or pause indicated */
logf("pcmbuf_pcm_callback: no more chunks");
last_chunksize = 0;
*size = 0;
@@ -694,8 +705,8 @@
logf("pcmbuf_play_start");
last_chunksize = read_chunk->size;
pcmbuf_unplayed_bytes -= last_chunksize;
- mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK,
- pcmbuf_pcm_callback, NULL, 0);
+ mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, pcmbuf_pcm_callback,
+ read_chunk->addr, last_chunksize);
}
}
@@ -1087,58 +1098,75 @@
/* Quiet-down the channel if 'shhh' is true or else play at normal level */
void pcmbuf_soft_mode(bool shhh)
{
+ /* "Hate this" alert (messing with IRQ in app code): Have to block
+ the tick or improper order could leave volume in soft mode if
+ fading reads the old value first but updates after us. */
+ int oldlevel = disable_irq_save();
soft_mode = shhh;
pcmbuf_update_volume();
+ restore_irq(oldlevel);
}
-/* Fade channel in or out */
+/* Tick that does the fade for the playback channel */
+static void pcmbuf_fade_tick(void)
+{
+ /* ~1/3 second for full range fade */
+ const unsigned int fade_step = MIX_AMP_UNITY / (HZ / 3);
+
+ if (fade_state == PCM_FADING_IN)
+ fade_vol += MIN(fade_step, MIX_AMP_UNITY - fade_vol);
+ else if (fade_state == PCM_FADING_OUT)
+ fade_vol -= MIN(fade_step, fade_vol - MIX_AMP_MUTE);
+
+ pcmbuf_update_volume();
+
+ if (fade_vol == MIX_AMP_MUTE || fade_vol == MIX_AMP_UNITY)
+ {
+ /* Fade is complete */
+ tick_remove_task(pcmbuf_fade_tick);
+
+ if (fade_state == PCM_FADING_OUT)
+ {
+ /* Tell PCM to stop at its earliest convenience */
+ fade_out_complete = true;
+ }
+
+ fade_state = PCM_NOT_FADING;
+ }
+}
+
+/* Fade channel in or out in the background - must pause it first */
void pcmbuf_fade(bool fade, bool in)
{
+ pcm_play_lock();
+
+ if (fade_state != PCM_NOT_FADING)
+ tick_remove_task(pcmbuf_fade_tick);
+
+ fade_out_complete = false;
+
+ pcm_play_unlock();
+
if (!fade)
{
/* Simply set the level */
+ fade_state = PCM_NOT_FADING;
fade_vol = in ? MIX_AMP_UNITY : MIX_AMP_MUTE;
+ pcmbuf_update_volume();
}
else
{
- /* Start from the opposing end */
- fade_vol = in ? MIX_AMP_MUTE : MIX_AMP_UNITY;
- }
-
- pcmbuf_update_volume();
-
- if (fade)
- {
- /* Do this on thread for now */
-#ifdef HAVE_PRIORITY_SCHEDULING
- int old_prio = thread_set_priority(thread_self(), PRIORITY_REALTIME);
-#endif
-
- while (1)
- {
- /* Linear fade actually sounds better */
- if (in)
- fade_vol += MIN(MIX_AMP_UNITY/16, MIX_AMP_UNITY - fade_vol);
- else
- fade_vol -= MIN(MIX_AMP_UNITY/16, fade_vol - MIX_AMP_MUTE);
-
- pcmbuf_update_volume();
-
- if (fade_vol > MIX_AMP_MUTE && fade_vol < MIX_AMP_UNITY)
- {
- sleep(0);
- continue;
- }
-
- break;
- }
-
-#ifdef HAVE_PRIORITY_SCHEDULING
- thread_set_priority(thread_self(), old_prio);
-#endif
+ /* Set direction and resume fade from current point */
+ fade_state = in ? PCM_FADING_IN : PCM_FADING_OUT;
+ tick_add_task(pcmbuf_fade_tick);
}
}
+/* Return 'true' if fade is in progress */
+bool pcmbuf_fading(void)
+{
+ return fade_state != PCM_NOT_FADING;
+}
/** Misc */
diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h
index b7f5a3c..a5cd316 100644
--- a/apps/pcmbuf.h
+++ b/apps/pcmbuf.h
@@ -65,6 +65,7 @@
/* Misc */
void pcmbuf_fade(bool fade, bool in);
+bool pcmbuf_fading(void);
void pcmbuf_soft_mode(bool shhh);
bool pcmbuf_is_lowdata(void);
void pcmbuf_set_low_latency(bool state);
diff --git a/apps/playback.c b/apps/playback.c
index a38534a..3adf6f6 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -1100,6 +1100,14 @@
pcmbuf_play_stop();
}
+/* Wait for any in-progress fade to complete */
+static void audio_wait_fade_complete(void)
+{
+ /* Just loop until it's done */
+ while (pcmbuf_fading())
+ sleep(0);
+}
+
/* End the ff/rw mode */
static void audio_ff_rewind_end(void)
{
@@ -2439,7 +2447,12 @@
if (play_status == PLAY_STOPPED)
return;
- pcmbuf_fade(global_settings.fade_on_stop, false);
+ bool do_fade = global_settings.fade_on_stop && filling != STATE_ENDED;
+
+ pcmbuf_fade(do_fade, false);
+
+ /* Wait for fade-out */
+ audio_wait_fade_complete();
/* Stop the codec and unload it */
halt_decoding_track(true);
@@ -2480,21 +2493,6 @@
if (play_status == PLAY_STOPPED || pause == (play_status == PLAY_PAUSED))
return;
- bool const do_fade = global_settings.fade_on_stop;
-
- if (pause)
- pcmbuf_fade(do_fade, false);
-
- if (!ff_rw_mode)
- {
- /* Not in ff/rw mode - may set the state (otherwise this could make
- old data play because seek hasn't completed and cleared it) */
- pcmbuf_pause(pause);
- }
-
- if (!pause)
- pcmbuf_fade(do_fade, true);
-
play_status = pause ? PLAY_PAUSED : PLAY_PLAYING;
if (!pause && codec_skip_pending)
@@ -2502,6 +2500,16 @@
/* Actually do the skip that is due - resets the status flag */
audio_on_codec_complete(codec_skip_status);
}
+
+ bool do_fade = global_settings.fade_on_stop;
+
+ pcmbuf_fade(do_fade, !pause);
+
+ if (!ff_rw_mode && !(do_fade && pause))
+ {
+ /* Not in ff/rw mode - can actually change the audio state now */
+ pcmbuf_pause(pause);
+ }
}
/* Skip a certain number of tracks forwards or backwards
@@ -2644,6 +2652,8 @@
ff_rw_mode = true;
+ audio_wait_fade_complete();
+
if (play_status == PLAY_PAUSED)
return;