diff --git a/apps/features.txt b/apps/features.txt index 2262f7502e..bafaa11599 100644 --- a/apps/features.txt +++ b/apps/features.txt @@ -296,3 +296,7 @@ hibylinux (CONFIG_KEYPAD == IRIVER_H10_PAD) clear_settings_on_hold #endif + +#if defined(HAVE_PERCEPTUAL_VOLUME) +perceptual_volume +#endif diff --git a/apps/gui/list.c b/apps/gui/list.c index 83d12289f2..652279a9de 100644 --- a/apps/gui/list.c +++ b/apps/gui/list.c @@ -663,12 +663,10 @@ bool gui_synclist_do_button(struct gui_synclist * lists, int *actionptr) #ifdef HAVE_VOLUME_IN_LIST case ACTION_LIST_VOLUP: - global_settings.volume += sound_steps(SOUND_VOLUME); - setvol(); + adjust_volume(1); return true; case ACTION_LIST_VOLDOWN: - global_settings.volume -= sound_steps(SOUND_VOLUME); - setvol(); + adjust_volume(-1); return true; #endif case ACTION_STD_PREV: diff --git a/apps/gui/quickscreen.c b/apps/gui/quickscreen.c index 356f74b283..221dfe3111 100644 --- a/apps/gui/quickscreen.c +++ b/apps/gui/quickscreen.c @@ -378,14 +378,12 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter else if (button == button_enter) can_quit = true; else if (button == ACTION_QS_VOLUP) { - global_settings.volume += sound_steps(SOUND_VOLUME); - setvol(); + adjust_volume(1); FOR_NB_SCREENS(i) skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC); } else if (button == ACTION_QS_VOLDOWN) { - global_settings.volume -= sound_steps(SOUND_VOLUME); - setvol(); + adjust_volume(-1); FOR_NB_SCREENS(i) skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC); } diff --git a/apps/gui/wps.c b/apps/gui/wps.c index 4b0c7c056f..fe656034b9 100644 --- a/apps/gui/wps.c +++ b/apps/gui/wps.c @@ -841,9 +841,9 @@ long gui_wps_show(void) case ACTION_WPS_VOLUP: /* fall through */ case ACTION_WPS_VOLDOWN: if (button == ACTION_WPS_VOLUP) - global_settings.volume += sound_steps(SOUND_VOLUME); + adjust_volume(1); else - global_settings.volume -= sound_steps(SOUND_VOLUME); + adjust_volume(-1); setvol(); FOR_NB_SCREENS(i) diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 3ad2921abe..232541e33f 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -15163,7 +15163,7 @@ id: LANG_DIRECT - desc: in the pictureflow settings + desc: in the pictureflow settings, also a volume adjustment mode user: core *: "Direct" @@ -16559,3 +16559,54 @@ *: "Play Last Shuffled" + + id: LANG_VOLUME_ADJUST_MODE + desc: in system settings + user: core + + *: none + perceptual_volume: "Volume Adjustment Mode" + + + *: none + perceptual_volume: "Volume Adjustment Mode" + + + *: none + perceptual_volume: "Volume Adjustment Mode" + + + + id: LANG_VOLUME_ADJUST_NORM_STEPS + desc: in system settings + user: core + + *: none + perceptual_volume: "Number of Volume Steps" + + + *: none + perceptual_volume: "Number of Volume Steps" + + + *: none + perceptual_volume: "Number of Volume Steps" + + + + id: LANG_PERCEPTUAL + desc: in system settings -> volume adjustment mode + user: core + + *: none + perceptual_volume: "Perceptual" + + + *: none + perceptual_volume: "Perceptual" + + + *: none + perceptual_volume: "Perceptual" + + diff --git a/apps/menus/settings_menu.c b/apps/menus/settings_menu.c index 9d1314c269..bfb69a9942 100644 --- a/apps/menus/settings_menu.c +++ b/apps/menus/settings_menu.c @@ -338,6 +338,11 @@ MAKE_MENU(limits_menu, ID2P(LANG_LIMITS_MENU), 0, Icon_NOICON, ,&default_glyphs ); +#ifdef HAVE_PERCEPTUAL_VOLUME +/* Volume adjustment */ +MENUITEM_SETTING(volume_adjust_mode, &global_settings.volume_adjust_mode, NULL); +MENUITEM_SETTING(volume_adjust_norm_steps, &global_settings.volume_adjust_norm_steps, NULL); +#endif /* Keyclick menu */ MENUITEM_SETTING(keyclick, &global_settings.keyclick, NULL); @@ -424,6 +429,10 @@ MAKE_MENU(system_menu, ID2P(LANG_SYSTEM), &disk_menu, #endif &limits_menu, +#ifdef HAVE_PERCEPTUAL_VOLUME + &volume_adjust_mode, + &volume_adjust_norm_steps, +#endif #ifdef HAVE_QUICKSCREEN &shortcuts_replaces_quickscreen, #endif diff --git a/apps/misc.c b/apps/misc.c index fd840749cb..e10fceb9af 100644 --- a/apps/misc.c +++ b/apps/misc.c @@ -824,6 +824,113 @@ void setvol(void) settings_save(); } +#ifdef HAVE_PERCEPTUAL_VOLUME +static short norm_tab[MAX_NORM_VOLUME_STEPS+2]; +static int norm_tab_num_steps; +static int norm_tab_size; + +static void update_norm_tab(void) +{ + const int lim = global_settings.volume_adjust_norm_steps; + if (lim == norm_tab_num_steps) + return; + norm_tab_num_steps = lim; + + const int min = sound_min(SOUND_VOLUME); + const int max = sound_max(SOUND_VOLUME); + const int step = sound_steps(SOUND_VOLUME); + + /* Ensure the table contains the minimum volume */ + norm_tab[0] = min; + norm_tab_size = 1; + + for (int i = 0; i < lim; ++i) + { + int vol = from_normalized_volume(i, min, max, lim); + int rem = vol % step; + + vol -= rem; + if (abs(rem) > step/2) + vol += rem < 0 ? -step : step; + + /* Add volume step, ignoring any duplicate entries that may + * occur due to rounding */ + if (vol != norm_tab[norm_tab_size-1]) + norm_tab[norm_tab_size++] = vol; + } + + /* Ensure the table contains the maximum volume */ + if (norm_tab[norm_tab_size-1] != max) + norm_tab[norm_tab_size++] = max; +} + +void set_normalized_volume(int vol) +{ + update_norm_tab(); + + if (vol < 0) + vol = 0; + if (vol >= norm_tab_size) + vol = norm_tab_size - 1; + + global_settings.volume = norm_tab[vol]; +} + +int get_normalized_volume(void) +{ + update_norm_tab(); + + int a = 0, b = norm_tab_size - 1; + while (a != b) + { + int i = (a + b + 1) / 2; + if (global_settings.volume < norm_tab[i]) + b = i - 1; + else + a = i; + } + + return a; +} +#else +void set_normalized_volume(int vol) +{ + global_settings.volume = vol * sound_steps(SOUND_VOLUME); +} + +int get_normalized_volume(void) +{ + return global_settings.volume / sound_steps(SOUND_VOLUME); +} +#endif + +void adjust_volume(int steps) +{ +#ifdef HAVE_PERCEPTUAL_VOLUME + adjust_volume_ex(steps, global_settings.volume_adjust_mode); +#else + adjust_volume_ex(steps, VOLUME_ADJUST_DIRECT); +#endif +} + +void adjust_volume_ex(int steps, enum volume_adjust_mode mode) +{ + switch (mode) + { + case VOLUME_ADJUST_PERCEPTUAL: +#ifdef HAVE_PERCEPTUAL_VOLUME + set_normalized_volume(get_normalized_volume() + steps); + break; +#endif + case VOLUME_ADJUST_DIRECT: + default: + global_settings.volume += steps * sound_steps(SOUND_VOLUME); + break; + } + + setvol(); +} + char* strrsplt(char* str, int c) { char* s = strrchr(str, c); diff --git a/apps/misc.h b/apps/misc.h index 72b8735c8a..b7a9a5c42c 100644 --- a/apps/misc.h +++ b/apps/misc.h @@ -137,8 +137,22 @@ void check_bootfile(bool do_rolo); #endif #endif +enum volume_adjust_mode +{ + VOLUME_ADJUST_DIRECT, /* adjust in units of the volume step size */ + VOLUME_ADJUST_PERCEPTUAL, /* adjust using perceptual steps */ +}; + +/* min/max values for global_settings.volume_adjust_norm_steps */ +#define MIN_NORM_VOLUME_STEPS 10 +#define MAX_NORM_VOLUME_STEPS 100 + /* check range, set volume and save settings */ void setvol(void); +void set_normalized_volume(int vol); +int get_normalized_volume(void); +void adjust_volume(int steps); +void adjust_volume_ex(int steps, enum volume_adjust_mode mode); #ifdef HAVE_LCD_COLOR int hex_to_rgb(const char* hex, int* color); diff --git a/apps/plugin.c b/apps/plugin.c index 00fac21b8d..cdbe340ddd 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -828,6 +828,7 @@ static const struct plugin_api rockbox_api = { #if defined(HAVE_TAGCACHE) tagtree_subentries_do_action, #endif + adjust_volume, }; static int plugin_buffer_handle; diff --git a/apps/plugin.h b/apps/plugin.h index 20df7e72f2..286a5e2794 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -158,7 +158,7 @@ int plugin_open(const char *plugin, const char *parameter); #define PLUGIN_MAGIC 0x526F634B /* RocK */ /* increase this every time the api struct changes */ -#define PLUGIN_API_VERSION 264 +#define PLUGIN_API_VERSION 265 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any @@ -954,6 +954,7 @@ struct plugin_api { #ifdef HAVE_TAGCACHE bool (*tagtree_subentries_do_action)(bool (*action_cb)(const char *file_name)); #endif + void (*adjust_volume)(int steps); }; /* plugin header */ diff --git a/apps/plugins/lrcplayer.c b/apps/plugins/lrcplayer.c index 71e5310638..de31733671 100644 --- a/apps/plugins/lrcplayer.c +++ b/apps/plugins/lrcplayer.c @@ -2625,16 +2625,10 @@ static int handle_button(void) ff_rewind(0, false); break; case ACTION_WPS_VOLDOWN: - limit = rb->sound_min(SOUND_VOLUME); - if (--rb->global_settings->volume < limit) - rb->global_settings->volume = limit; - rb->sound_set(SOUND_VOLUME, rb->global_settings->volume); + rb->adjust_volume(-1); break; case ACTION_WPS_VOLUP: - limit = rb->sound_max(SOUND_VOLUME); - if (++rb->global_settings->volume > limit) - rb->global_settings->volume = limit; - rb->sound_set(SOUND_VOLUME, rb->global_settings->volume); + rb->adjust_volume(1); break; case ACTION_WPS_CONTEXT: ret = LRC_GOTO_EDITOR; diff --git a/apps/plugins/mikmod/mikmod.c b/apps/plugins/mikmod/mikmod.c index 6622b5fdb6..65633c0ad1 100644 --- a/apps/plugins/mikmod/mikmod.c +++ b/apps/plugins/mikmod/mikmod.c @@ -710,7 +710,6 @@ static void mm_errorhandler(void) static int playfile(char* filename) { - int vol = 0; int button; int retval = PLUGIN_OK; bool changingpos = false; @@ -789,13 +788,8 @@ static int playfile(char* filename) } break; } - vol = rb->global_settings->volume; - if (vol < rb->sound_max(SOUND_VOLUME)) - { - vol++; - rb->sound_set(SOUND_VOLUME, vol); - rb->global_settings->volume = vol; - } + + rb->adjust_volume(1); break; case ACTION_WPS_VOLDOWN: @@ -808,13 +802,8 @@ static int playfile(char* filename) } break; } - vol = rb->global_settings->volume; - if (vol > rb->sound_min(SOUND_VOLUME)) - { - vol--; - rb->sound_set(SOUND_VOLUME, vol); - rb->global_settings->volume = vol; - } + + rb->adjust_volume(-1); break; case ACTION_WPS_SKIPPREV: diff --git a/apps/plugins/sdl/SDL_mixer/timidity/playmidi.c b/apps/plugins/sdl/SDL_mixer/timidity/playmidi.c index 1638732dc5..38f7109b13 100644 --- a/apps/plugins/sdl/SDL_mixer/timidity/playmidi.c +++ b/apps/plugins/sdl/SDL_mixer/timidity/playmidi.c @@ -21,6 +21,8 @@ #include "tables.h" +/* ROCKBOX HACK: avoid a conflict with adjust_volume() in misc.h */ +#define adjust_volume adjust_midi_volume static int opt_expression_curve = 2; static int opt_volume_curve = 2; diff --git a/apps/settings.h b/apps/settings.h index 1ad1923c73..ca10c45d5f 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -855,6 +855,11 @@ struct user_settings #endif int volume_limit; /* maximum volume limit */ +#ifdef HAVE_PERCEPTUAL_VOLUME + int volume_adjust_mode; + int volume_adjust_norm_steps; +#endif + int surround_enabled; int surround_balance; int surround_fx1; diff --git a/apps/settings_list.c b/apps/settings_list.c index 60ac4192fa..315f39b21f 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -40,6 +40,7 @@ #include "powermgmt.h" #include "kernel.h" #include "open_plugin.h" +#include "misc.h" #ifdef HAVE_REMOTE_LCD #include "lcd-remote.h" #endif @@ -1057,6 +1058,16 @@ const struct settings_list settings[] = { MAX_FILES_IN_DIR_STEP /* min */, MAX_FILES_IN_DIR_MAX, MAX_FILES_IN_DIR_STEP, NULL, NULL, NULL), +#ifdef HAVE_PERCEPTUAL_VOLUME + CHOICE_SETTING(0, volume_adjust_mode, LANG_VOLUME_ADJUST_MODE, + VOLUME_ADJUST_DIRECT, "volume adjustment mode", + "direct,perceptual", NULL, 2, + ID2P(LANG_DIRECT), ID2P(LANG_PERCEPTUAL)), + INT_SETTING_NOWRAP(0, volume_adjust_norm_steps, LANG_VOLUME_ADJUST_NORM_STEPS, + 50, "perceptual volume step count", UNIT_INT, + MIN_NORM_VOLUME_STEPS, MAX_NORM_VOLUME_STEPS, 5, + NULL, NULL, NULL), +#endif /* use this setting for user code even if there's no exchangable battery * support enabled */ #if BATTERY_CAPACITY_INC > 0 diff --git a/firmware/export/config.h b/firmware/export/config.h index 2ec0b7878f..0882cad61c 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -1319,6 +1319,10 @@ Lyre prototype 1 */ # define HAVE_SCREENDUMP #endif +#if !defined(BOOTLOADER) && MEMORYSIZE > 2 +# define HAVE_PERCEPTUAL_VOLUME +#endif + /* null audiohw setting macro for when codec header is included for reasons other than audio support */ #define AUDIOHW_SETTING(name, us, nd, st, minv, maxv, defv, expr...) diff --git a/manual/configure_rockbox/system_options.tex b/manual/configure_rockbox/system_options.tex index ba80a6e6e4..d4a282b445 100755 --- a/manual/configure_rockbox/system_options.tex +++ b/manual/configure_rockbox/system_options.tex @@ -137,6 +137,23 @@ This sub menu relates to limits in the Rockbox operating system. \item LAN party computer $\rightarrow$ \dap $\rightarrow$ human \end{itemize} } +\opt{perceptual_volume}{ + \subsection{Volume Adjustment Mode} + This setting selects the method used to adjust volume with \ButtonVolUp{} and + \ButtonVolDown{}. In \setting{Direct} mode each volume step changes the volume + by a fixed number of decibels (dB). + + In \setting{Perceptual} mode, the hardware volume range is divided into a + number of steps, controlled by the \setting{Number of Volume Steps} option. + Each step changes the volume by a variable number of decibels (dB) so the + perceived loudness changes by about the same amount at each step. The dB + change is smaller at high volumes and larger at low volumes, so a large + range of low volumes are effectively compressed into a smaller number of + volume steps. + + \setting{Volume Adjustment Mode} does not affect how volume is displayed by + themes. +} \opt{quickscreen}{ \subsection{Use Shortcuts Menu Instead of Quick Screen} This option activates the shortcuts menu instead of opening the quick screen when enabled.