diff --git a/apps/gui/wps.c b/apps/gui/wps.c
index 78e49ac4f8..0e52323dc2 100644
--- a/apps/gui/wps.c
+++ b/apps/gui/wps.c
@@ -829,6 +829,8 @@ long gui_wps_show(void)
if (retval == ONPLAY_MAINMENU
|| !audio_status())
return GO_TO_ROOT;
+ else if (retval == ONPLAY_REVEAL_FILE)
+ return GO_TO_FILEBROWSER;
else if (retval == ONPLAY_PLAYLIST)
return GO_TO_PLAYLIST_VIEWER;
else if (retval == ONPLAY_PLUGIN)
diff --git a/apps/lang/english-us.lang b/apps/lang/english-us.lang
index db078e77af..d1f8c108ce 100644
--- a/apps/lang/english-us.lang
+++ b/apps/lang/english-us.lang
@@ -16984,3 +16984,73 @@
*: "U S B"
+
+ id: LANG_COUNTDOWN_TIMER_SET
+ desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration
+ user: core
+
+ *: "SET TIMER"
+
+
+ *: "SET TIMER"
+
+
+ *: "Set timer"
+
+
+
+ id: LANG_COUNTDOWN_TIMER_RUNNING
+ desc: countdown_timer plugin - status label shown while the countdown is active
+ user: core
+
+ *: "RUNNING"
+
+
+ *: "RUNNING"
+
+
+ *: "Running"
+
+
+
+ id: LANG_COUNTDOWN_TIMER_PAUSED
+ desc: countdown_timer plugin - status label shown while the countdown is paused
+ user: core
+
+ *: "PAUSED"
+
+
+ *: "PAUSED"
+
+
+ *: "Paused"
+
+
+
+ id: LANG_COUNTDOWN_TIMER_OVERTIME
+ desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up
+ user: core
+
+ *: "OVERTIME"
+
+
+ *: "OVERTIME"
+
+
+ *: "Overtime"
+
+
+
+ id: LANG_COUNTDOWN_TIMER_FINISHED
+ desc: countdown_timer plugin - status label shown at the moment the countdown expires
+ user: core
+
+ *: "FINISHED"
+
+
+ *: "FINISHED"
+
+
+ *: "Finished"
+
+
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index aeedbc2541..45ed8938fc 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -17057,3 +17057,115 @@
*: "U S B"
+
+ id: LANG_FILE_NOT_FOUND
+ desc: When file does not exist
+ user: core
+
+ *: "File not found"
+
+
+ *: "File not found"
+
+
+ *: "File not found"
+
+
+
+ id: LANG_SHOW_IN_FILES
+ desc: Reveal item in File Browser
+ user: core
+
+ *: "Show in Files"
+
+
+ *: "Show in Files"
+
+
+ *: "Show in Files"
+
+
+
+ id: LANG_CHANNEL_SWAP
+ desc: in sound_settings
+ user: core
+
+ *: "Swap Left & Right"
+
+
+ *: "Swap Left & Right"
+
+
+ *: "Swap Left & Right"
+
+
+
+ id: LANG_COUNTDOWN_TIMER_SET
+ desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration
+ user: core
+
+ *: "SET TIMER"
+
+
+ *: "SET TIMER"
+
+
+ *: "Set timer"
+
+
+
+ id: LANG_COUNTDOWN_TIMER_RUNNING
+ desc: countdown_timer plugin - status label shown while the countdown is active
+ user: core
+
+ *: "RUNNING"
+
+
+ *: "RUNNING"
+
+
+ *: "Running"
+
+
+
+ id: LANG_COUNTDOWN_TIMER_PAUSED
+ desc: countdown_timer plugin - status label shown while the countdown is paused
+ user: core
+
+ *: "PAUSED"
+
+
+ *: "PAUSED"
+
+
+ *: "Paused"
+
+
+
+ id: LANG_COUNTDOWN_TIMER_OVERTIME
+ desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up
+ user: core
+
+ *: "OVERTIME"
+
+
+ *: "OVERTIME"
+
+
+ *: "Overtime"
+
+
+
+ id: LANG_COUNTDOWN_TIMER_FINISHED
+ desc: countdown_timer plugin - status label shown at the moment the countdown expires
+ user: core
+
+ *: "FINISHED"
+
+
+ *: "FINISHED"
+
+
+ *: "Finished"
+
+
diff --git a/apps/onplay.c b/apps/onplay.c
index 15b519871f..82b5ab8931 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -897,6 +897,26 @@ static bool onplay_load_plugin(void *param)
return false;
}
+static int reveal(void)
+{
+#ifdef HAVE_TAGCACHE
+ if (!prepare_database_sel(NULL))
+ return 0;
+#endif
+ if (!file_exists(selected_file.path))
+ {
+ splash(HZ*2, ID2P(LANG_FILE_NOT_FOUND));
+ return 0;
+ }
+
+ strmemccpy(global_status.browse_last_folder, selected_file.path,
+ sizeof global_status.browse_last_folder);
+ onplay_result = ONPLAY_REVEAL_FILE;
+ return 0;
+}
+
+MENUITEM_FUNCTION(reveal_item, 0, ID2P(LANG_SHOW_IN_FILES),
+ reveal, clipboard_callback, Icon_file_view_menu);
MENUITEM_FUNCTION(list_viewers_item, 0, ID2P(LANG_ONPLAY_OPEN_WITH),
list_viewers, clipboard_callback, Icon_NOICON);
MENUITEM_FUNCTION_W_PARAM(properties_item, 0, ID2P(LANG_PROPERTIES),
@@ -1033,7 +1053,8 @@ static int clipboard_callback(int action,
if (selected_file.context == CONTEXT_ID3DB)
{
if (this_item == &track_info_item ||
- this_item == &pictureflow_item)
+ this_item == &pictureflow_item ||
+ this_item == &reveal_item)
return action;
return ACTION_EXIT_MENUITEM;
}
@@ -1088,7 +1109,9 @@ static int clipboard_callback(int action,
if (*tree_get_context()->dirfilter != SHOW_M3U)
return action;
}
- else if (this_item == &delete_file_item)
+ else if (this_item == &delete_file_item ||
+ (this_item == &reveal_item &&
+ selected_file.context == CONTEXT_WPS))
return action;
#if LCD_DEPTH > 1
else if (this_item == &set_backdrop_item)
@@ -1124,8 +1147,8 @@ MAKE_ONPLAYMENU( wps_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE),
#endif
&bookmark_menu,
&plugin_item,
- &browse_id3_item, &list_viewers_item,
- &delete_file_item, &view_cue_item,
+ &browse_id3_item,
+ &reveal_item, &view_cue_item,
#ifdef HAVE_PITCHCONTROL
&pitch_menu,
#endif
@@ -1162,6 +1185,7 @@ MAKE_ONPLAYMENU( tree_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE),
&rename_file_item, &clipboard_cut_item, &clipboard_copy_item,
&clipboard_paste_item, &delete_file_item, &delete_dir_item,
&list_viewers_item, &create_dir_item, &properties_item, &track_info_item,
+ &reveal_item,
#ifdef HAVE_TAGCACHE
&pictureflow_item,
#endif
diff --git a/apps/onplay.h b/apps/onplay.h
index 26b1247f7b..4e5e6eeb69 100644
--- a/apps/onplay.h
+++ b/apps/onplay.h
@@ -38,6 +38,7 @@ enum {
ONPLAY_MAINMENU = -1,
ONPLAY_OK = 0,
ONPLAY_RELOAD_DIR,
+ ONPLAY_REVEAL_FILE,
ONPLAY_START_PLAY,
ONPLAY_PLAYLIST,
ONPLAY_PLUGIN,
diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c
index 88f2590c3e..392131a8a9 100644
--- a/apps/playlist_viewer.c
+++ b/apps/playlist_viewer.c
@@ -515,9 +515,15 @@ static void format_line(struct playlist_entry* track, char* str,
int len)
{
char *id3viewc = NULL;
- char *skipped = "";
+ char *skipped, *prefix, *suffix;
+ skipped = prefix = suffix = "";
if (track->attr & PLAYLIST_ATTR_SKIPPED)
skipped = "(ERR) ";
+ if (track->index == viewer.current_playing_track)
+ {
+ prefix = "[";
+ suffix = "]";
+ }
if (!(track->attr & PLAYLIST_ATTR_RETRIEVE_ID3_ATTEMPTED) &&
(global_settings.playlist_viewer_track_display ==
PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM ||
@@ -591,9 +597,10 @@ static void format_line(struct playlist_entry* track, char* str,
format_name(name, track->name, sizeof(name));
if (global_settings.playlist_viewer_indices)
/* Display playlist index */
- snprintf(str, len, "%d. %s%s", track->display_index, skipped, name);
+ snprintf(str, len, "%s%d. %s%s%s",
+ prefix, track->display_index, skipped, name, suffix);
else
- snprintf(str, len, "%s%s", skipped, name);
+ snprintf(str, len, "%s%s%s%s", prefix, skipped, name, suffix);
}
else
{
@@ -603,9 +610,10 @@ static void format_line(struct playlist_entry* track, char* str,
}
if (global_settings.playlist_viewer_indices)
/* Display playlist index */
- snprintf(str, len, "%d. %s%s", track->display_index, skipped, id3viewc);
+ snprintf(str, len, "%s%d. %s%s%s",
+ prefix, track->display_index, skipped, id3viewc, suffix);
else
- snprintf(str, len, "%s%s", skipped, id3viewc);
+ snprintf(str, len, "%s%s%s%s", prefix, skipped, id3viewc, suffix);
}
}
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index c9b1781a15..bf646a5bf9 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -21,6 +21,7 @@ chopper,games
clix,games
clock,apps
codebuster,games
+countdown_timer,apps
credits,viewers
cube,demos
cue_playlist,viewers
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 3a57e3f9e5..797601e4dd 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -7,6 +7,7 @@ db_folder_select.c
tagcache/tagcache.c
#endif
chessclock.c
+countdown_timer.c
credits.c
cube.c
cue_playlist.c
diff --git a/apps/plugins/countdown_timer.c b/apps/plugins/countdown_timer.c
new file mode 100644
index 0000000000..b520047bf3
--- /dev/null
+++ b/apps/plugins/countdown_timer.c
@@ -0,0 +1,242 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2026 Teun van Dalen
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#include "plugin.h"
+#include "lib/pluginlib_actions.h"
+#include "lib/pluginlib_exit.h"
+
+const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
+
+#define MAX_MINUTES 99
+#define MAX_SECONDS 59
+
+typedef enum {
+ STATE_SETUP,
+ STATE_RUNNING,
+ STATE_PAUSED,
+ STATE_FINISHED,
+} timer_state_t;
+
+typedef struct {
+ int set_minutes;
+ int set_seconds;
+ int current_field;
+ long remaining_ticks;
+ long last_tick;
+ timer_state_t state;
+} timer_ctx_t;
+
+static inline int get_button(void)
+{
+ return pluginlib_getaction(HZ / 10, plugin_contexts,
+ ARRAYLEN(plugin_contexts));
+}
+
+static void draw(const timer_ctx_t *ctx)
+{
+ char time_str[12];
+ const char *status_str;
+ int disp_min, disp_sec;
+ int w, h, x, y;
+
+ rb->lcd_clear_display();
+
+ if (ctx->state == STATE_SETUP) {
+ disp_min = ctx->set_minutes;
+ disp_sec = ctx->set_seconds;
+ if (ctx->current_field == 0)
+ rb->snprintf(time_str, sizeof(time_str), "[%02d]:%02d",
+ disp_min, disp_sec);
+ else
+ rb->snprintf(time_str, sizeof(time_str), "%02d:[%02d]",
+ disp_min, disp_sec);
+ status_str = rb->str(LANG_COUNTDOWN_TIMER_SET);
+ } else if (ctx->state == STATE_FINISHED) {
+ rb->snprintf(time_str, sizeof(time_str), "00:00");
+ status_str = rb->str(LANG_COUNTDOWN_TIMER_FINISHED);
+ } else {
+ if (ctx->remaining_ticks >= 0) {
+ int remaining_secs = (ctx->remaining_ticks + HZ - 1) / HZ;
+ disp_min = remaining_secs / 60;
+ disp_sec = remaining_secs % 60;
+ rb->snprintf(time_str, sizeof(time_str), "%02d:%02d",
+ disp_min, disp_sec);
+ } else {
+ int over_secs = ((-ctx->remaining_ticks) + HZ - 1) / HZ;
+ disp_min = over_secs / 60;
+ disp_sec = over_secs % 60;
+ rb->snprintf(time_str, sizeof(time_str), "-%02d:%02d",
+ disp_min, disp_sec);
+ }
+
+ if (ctx->state == STATE_RUNNING && ctx->remaining_ticks < 0)
+ status_str = rb->str(LANG_COUNTDOWN_TIMER_OVERTIME);
+ else if (ctx->state == STATE_RUNNING)
+ status_str = rb->str(LANG_COUNTDOWN_TIMER_RUNNING);
+ else
+ status_str = rb->str(LANG_COUNTDOWN_TIMER_PAUSED);
+ }
+
+ /* Draw status string above center */
+ rb->lcd_getstringsize(status_str, &w, &h);
+ x = (LCD_WIDTH - w) / 2;
+ y = (LCD_HEIGHT / 2) - h - 2;
+ rb->lcd_putsxy(x, y, status_str);
+
+ /* Draw time string at center */
+ rb->lcd_getstringsize(time_str, &w, &h);
+ x = (LCD_WIDTH - w) / 2;
+ y = LCD_HEIGHT / 2;
+ rb->lcd_putsxy(x, y, time_str);
+
+ rb->lcd_update();
+}
+
+enum plugin_status plugin_start(const void *parameter)
+{
+ timer_ctx_t ctx = {
+ .set_minutes = 10,
+ .set_seconds = 0,
+ .current_field = 0,
+ .remaining_ticks = 60 * HZ,
+ .last_tick = 0,
+ .state = STATE_SETUP,
+ };
+ bool overtime_alerted = false;
+ int button;
+
+ (void)parameter;
+
+ while (true) {
+ if (ctx.state == STATE_RUNNING) {
+ long now = *rb->current_tick;
+ ctx.remaining_ticks -= now - ctx.last_tick;
+ ctx.last_tick = now;
+
+ if (ctx.remaining_ticks < 0 && !overtime_alerted) {
+ overtime_alerted = true;
+ ctx.state = STATE_FINISHED;
+ draw(&ctx);
+ rb->audio_pause();
+#ifdef HAVE_BACKLIGHT
+ rb->backlight_on();
+#endif
+ rb->beep_play(1000, 200, 1000);
+ rb->sleep(HZ / 4);
+ rb->beep_play(1000, 200, 1000);
+ rb->sleep(HZ / 4);
+ rb->beep_play(1000, 400, 1000);
+ rb->sleep(HZ + HZ / 2);
+ rb->audio_resume();
+ ctx.state = STATE_RUNNING;
+ /* Reset last_tick so the beep/sleep time is not counted
+ * against remaining_ticks on the next iteration. */
+ ctx.last_tick = *rb->current_tick;
+ }
+ }
+
+ draw(&ctx);
+ button = get_button();
+
+ switch (ctx.state) {
+ case STATE_SETUP: {
+ bool time_changed = false;
+ switch (button) {
+#ifdef HAVE_SCROLLWHEEL
+ case PLA_SCROLL_FWD:
+ case PLA_SCROLL_FWD_REPEAT:
+#endif
+ case PLA_UP:
+ case PLA_UP_REPEAT:
+ if (ctx.current_field == 0)
+ ctx.set_minutes = (ctx.set_minutes + 1) % (MAX_MINUTES + 1);
+ else
+ ctx.set_seconds = (ctx.set_seconds + 1) % (MAX_SECONDS + 1);
+ time_changed = true;
+ break;
+#ifdef HAVE_SCROLLWHEEL
+ case PLA_SCROLL_BACK:
+ case PLA_SCROLL_BACK_REPEAT:
+#endif
+ case PLA_DOWN:
+ case PLA_DOWN_REPEAT:
+ if (ctx.current_field == 0)
+ ctx.set_minutes = (ctx.set_minutes + MAX_MINUTES) % (MAX_MINUTES + 1);
+ else
+ ctx.set_seconds = (ctx.set_seconds + MAX_SECONDS) % (MAX_SECONDS + 1);
+ time_changed = true;
+ break;
+ case PLA_LEFT:
+ case PLA_LEFT_REPEAT:
+ case PLA_RIGHT:
+ case PLA_RIGHT_REPEAT:
+ ctx.current_field = (ctx.current_field + 1) % 2;
+ break;
+ case PLA_SELECT:
+ if (ctx.set_minutes > 0 || ctx.set_seconds > 0) {
+ ctx.remaining_ticks = (ctx.set_minutes * 60 + ctx.set_seconds) * HZ;
+ ctx.last_tick = *rb->current_tick;
+ ctx.state = STATE_RUNNING;
+ }
+ break;
+ default:
+ exit_on_usb(button);
+ break;
+ }
+ if (time_changed)
+ ctx.remaining_ticks = (ctx.set_minutes * 60 + ctx.set_seconds) * HZ;
+ break;
+ }
+
+ case STATE_RUNNING:
+ switch (button) {
+ case PLA_SELECT:
+ ctx.state = STATE_PAUSED;
+ break;
+ default:
+ exit_on_usb(button);
+ break;
+ }
+ break;
+
+ case STATE_FINISHED:
+ exit_on_usb(button);
+ break;
+
+ case STATE_PAUSED:
+ switch (button) {
+ case PLA_SELECT:
+ ctx.last_tick = *rb->current_tick;
+ ctx.state = STATE_RUNNING;
+ break;
+ case PLA_UP:
+ case PLA_UP_REPEAT:
+ case PLA_CANCEL:
+ case PLA_EXIT:
+ return PLUGIN_OK;
+ default:
+ exit_on_usb(button);
+ break;
+ }
+ break;
+ }
+ }
+}
diff --git a/apps/settings_list.c b/apps/settings_list.c
index 826f85825d..59fd4537a0 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -1060,11 +1060,12 @@ const struct settings_list settings[] = {
/* 3-d enhancement effect */
CHOICE_SETTING(0, channel_config, LANG_CHANNEL_CONFIGURATION,
0,"channels",
- "stereo,mono,custom,mono left,mono right,karaoke",
- sound_set_channels, 6,
+ "stereo,mono,custom,mono left,mono right,karaoke,swap",
+ sound_set_channels, 7,
ID2P(LANG_CHANNEL_STEREO), ID2P(LANG_CHANNEL_MONO),
ID2P(LANG_CHANNEL_CUSTOM), ID2P(LANG_CHANNEL_LEFT),
- ID2P(LANG_CHANNEL_RIGHT), ID2P(LANG_CHANNEL_KARAOKE)),
+ ID2P(LANG_CHANNEL_RIGHT), ID2P(LANG_CHANNEL_KARAOKE),
+ ID2P(LANG_CHANNEL_SWAP)),
SOUND_SETTING(0, stereo_width, LANG_STEREO_WIDTH,
"stereo_width", SOUND_STEREO_WIDTH),
#ifdef AUDIOHW_HAVE_DEPTH_3D
diff --git a/apps/tree.c b/apps/tree.c
index 5df204bbf2..387a070e3a 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -975,6 +975,10 @@ static int dirbrowse(void)
return exit_to_new_screen(GO_TO_ROOT);
break;
+ case ONPLAY_REVEAL_FILE:
+ return exit_to_new_screen(GO_TO_FILEBROWSER);
+ break;
+
case ONPLAY_OK:
restore = do_restore_display;
break;
diff --git a/docs/CREDITS b/docs/CREDITS
index 06045db966..f1affc6eb8 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -760,6 +760,7 @@ Yegor Chernyshov
Javier Gutiérrez Gertrúdix
Sergey Puskov
Eivind Ødegård
+Teun van Dalen
The libmad team
The wavpack team
diff --git a/firmware/export/audiohw.h b/firmware/export/audiohw.h
index 65570abb5b..ec465ca7b7 100644
--- a/firmware/export/audiohw.h
+++ b/firmware/export/audiohw.h
@@ -674,6 +674,7 @@ enum AUDIOHW_CHANNEL_CONFIG
SOUND_CHAN_MONO_LEFT,
SOUND_CHAN_MONO_RIGHT,
SOUND_CHAN_KARAOKE,
+ SOUND_CHAN_SWAP,
SOUND_CHAN_NUM_MODES,
};
diff --git a/lib/rbcodec/dsp/channel_mode.c b/lib/rbcodec/dsp/channel_mode.c
index 7a83236648..86dc02e3a4 100644
--- a/lib/rbcodec/dsp/channel_mode.c
+++ b/lib/rbcodec/dsp/channel_mode.c
@@ -43,6 +43,8 @@ void channel_mode_proc_custom(struct dsp_proc_entry *this,
struct dsp_buffer **buf_p);
void channel_mode_proc_karaoke(struct dsp_proc_entry *this,
struct dsp_buffer **buf_p);
+void channel_mode_proc_swap(struct dsp_proc_entry *this,
+ struct dsp_buffer **buf_p);
static struct channel_mode_data
{
@@ -131,6 +133,25 @@ void channel_mode_proc_karaoke(struct dsp_proc_entry *this,
}
#endif /* CPU */
+void channel_mode_proc_swap(struct dsp_proc_entry *this,
+ struct dsp_buffer **buf_p)
+{
+ struct dsp_buffer *buf = *buf_p;
+ int32_t *sl = buf->p32[0];
+ int32_t *sr = buf->p32[1];
+ int count = buf->remcount;
+
+ do
+ {
+ int32_t l = *sl;
+ *sl++ = *sr;
+ *sr++ = l;
+ }
+ while (--count > 0);
+
+ (void)this;
+}
+
void channel_mode_proc_mono_left(struct dsp_proc_entry *this,
struct dsp_buffer **buf_p)
{
@@ -194,6 +215,7 @@ static void update_process_fn(struct dsp_proc_entry *this)
[SOUND_CHAN_MONO_LEFT] = channel_mode_proc_mono_left,
[SOUND_CHAN_MONO_RIGHT] = channel_mode_proc_mono_right,
[SOUND_CHAN_KARAOKE] = channel_mode_proc_karaoke,
+ [SOUND_CHAN_SWAP] = channel_mode_proc_swap,
};
this->process = fns[((struct channel_mode_data *)this->data)->mode];
diff --git a/manual/configure_rockbox/sound_settings.tex b/manual/configure_rockbox/sound_settings.tex
index f9c85b6fbb..a8e94737b9 100644
--- a/manual/configure_rockbox/sound_settings.tex
+++ b/manual/configure_rockbox/sound_settings.tex
@@ -178,6 +178,9 @@ change to customise your listening experience.
to make the singer sound centrally placed, this often (but not
always) has the effect of removing the voice track from a song. This
setting also very often has other undesirable effects on the sound.
+ \item[Swap Left \& Right.]
+ Plays the left channel in the right stereo channel, and the right
+ channel in the left stereo channel.
\end{description}
\section{Stereo Width}
diff --git a/manual/plugins/countdown_timer.tex b/manual/plugins/countdown_timer.tex
new file mode 100644
index 0000000000..c022203a92
--- /dev/null
+++ b/manual/plugins/countdown_timer.tex
@@ -0,0 +1,51 @@
+% $Id$ %
+\subsection{Countdown_timer}
+
+A countdown timer. Set the desired duration, start the countdown, and the
+\dap{} will alert you with a beep sequence when time is up. The timer
+defaults to 10 minutes on launch.
+
+\subsubsection{Setting the timer}
+
+When the plugin starts, the display shows the time to count down from with
+the active field highlighted in brackets (e.g.\ \texttt{[10]:00}).
+
+\begin{btnmap}
+ \opt{scrollwheel}{\PluginScrollFwd{} / \PluginScrollBack{} or }
+ \PluginUp{} / \PluginDown
+ \opt{HAVEREMOTEKEYMAP}{& \PluginRCUp{} / \PluginRCDown}
+ & Increase / decrease the selected field \\
+
+ \PluginLeft{} / \PluginRight
+ \opt{HAVEREMOTEKEYMAP}{& \PluginRCLeft{} / \PluginRCRight}
+ & Switch between the minutes and seconds fields \\
+
+ \PluginSelect
+ \opt{HAVEREMOTEKEYMAP}{& \PluginRCSelect}
+ & Start the countdown \\
+\end{btnmap}
+
+Minutes can be set from 0 to 99 and seconds from 0 to 59. The timer cannot
+be started if both fields are zero. Values wrap around when incremented or
+decremented past their limits.
+
+\subsubsection{Running the timer}
+
+Once started, the remaining time counts down in \texttt{MM:SS} format.
+
+\begin{btnmap}
+ \PluginSelect
+ \opt{HAVEREMOTEKEYMAP}{& \PluginRCSelect}
+ & Pause / resume the timer \\
+
+ \nopt{IPOD_4G_PAD,IPOD_3G_PAD}{\PluginCancel}
+ \opt{IPOD_4G_PAD,IPOD_3G_PAD}{\ButtonMenu}
+ \opt{HAVEREMOTEKEYMAP}{& \PluginRCCancel}
+ & Quit (only available while paused) \\
+\end{btnmap}
+
+When the countdown reaches zero the \dap{} pauses playback, turns on the
+backlight, and plays three beeps. After the alert the timer continues
+running into overtime, displaying the elapsed overtime as
+\texttt{-MM:SS}. Playback resumes automatically after the beep sequence.
+The timer can be quit by pausing it and pressing the quit button.
diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex
index d6601b8a5b..8ca4b86e6b 100644
--- a/manual/plugins/main.tex
+++ b/manual/plugins/main.tex
@@ -297,3 +297,5 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).}
\input{plugins/stopwatch.tex}
\input{plugins/text_editor.tex}
+
+\input{plugins/countdown_timer.tex}
diff --git a/manual/rockbox_interface/browsing_and_playing.tex b/manual/rockbox_interface/browsing_and_playing.tex
index 45a145ad3a..c6443a9351 100644
--- a/manual/rockbox_interface/browsing_and_playing.tex
+++ b/manual/rockbox_interface/browsing_and_playing.tex
@@ -143,7 +143,9 @@ each option pertains both to files and directories):
Deletes the currently selected file. This option applies only to files, and
not to directories. Rockbox will ask for confirmation before deleting a file.
Press \ActionYesNoAccept{}
- to confirm deletion or any other key to cancel.
+ to confirm deletion or any other key to cancel. When deleting a currently
+ playing file, the part of the file that has already been buffered (i.e. read
+ into the \daps\ memory) will continue playing. This may even be the whole track.
\item [Delete Directory.]
Deletes the currently selected directory and all of the files and subdirectories
it may contain. Deleted directories cannot be recovered. Use this feature with
diff --git a/manual/rockbox_interface/wps.tex b/manual/rockbox_interface/wps.tex
index 3ed0d961e6..918d62a436 100644
--- a/manual/rockbox_interface/wps.tex
+++ b/manual/rockbox_interface/wps.tex
@@ -254,15 +254,8 @@ from the WPS, %
\setting{Context Menu}. Then select \setting{Show Track Info}. The same steps
work in the Playlist Viewer as well.}
-\subsubsection{Open With...}
-This \setting{Open With} function is the same as the \setting{Open With}
-function in the file browser's \setting{Context Menu}.
-
-\subsubsection{Delete}
-Delete the currently playing file. The file will be deleted but the playback
-of the file will not stop immediately. Instead, the part of the file that
-has already been buffered (i.e. read into the \daps\ memory) will be played.
-This may even be the whole track.
+\subsubsection{Show in Files}
+Reveal the currently playing track in the \setting{File Browser}.
\opt{pitchscreen}{
\subsubsection{\label{sec:pitchscreen}Pitch}