Rockbox Technical Forums

Rockbox Development => Feature Ideas => Topic started by: tdb on December 27, 2016, 07:11:21 AM

Title: more advanced sleep timer
Post by: tdb on December 27, 2016, 07:11:21 AM
Hi

I have been using rockbox for many years almost daily and am very pleased with it. Use it mainly for listening to podcasts and audiobooks and appreciate the tons of features and settings (especially the bookmarking features).

I also use the sleeptimer function often as an aid to go to sleep with a podcast or audiobook. I usually need 15 minutes to go to sleep and that works fine most of the times, but it can happen that a podcast or audiobook wakes me up within that time frame due to some louder recorded sound/voice or sudden change of voice / speaker etc.
For that reason there are only a few podcasts I have chosen to 'talk me into sleep' and keep the sound level low but even then, it sometimes happens I wake up again before sleeptimer shuts down the device. 

A solution for this 'problem' might be to add a fade out feature for sleeptimer that takes a couple of minutes (preferably something that can be set by the user)
In practice that could mean I would change the length of the sleeptimer from 15 minutes to 20 or 25 minutes and starts very gradually to fade out after 10 minutes and it takes 10-15 minutes to reach volume 0 and shuts down.

That would make the sleeptimer an even more useful tool as a sleeping aid. I am pretty sure lots of folks are already using it as a sleeping aid, but a feature like this takes it to another level imo.
Maybe there is already something like this possible but I couldn't find it. The fade on stop option is too short and not specific for the sleep timer so that doesn't really help. 


 





Title: Re: more advanced sleep timer
Post by: Bilgus on December 28, 2016, 01:59:32 AM
Here is a lua script that should do what you want, feel free to tweak it to your hearts desire

set your sleep timer and start your songs
copy into a file called Slow_fade.lua or something like that and run it from the file browser
choose a timeout and it will reduce the volume over the span you selected
when it hits the minimum volume playback will stop

Code: [Select]
--Bilgus 12-2016
require "actions"
require "buttons"
TIMEOUT = 0
SOUND_VOLUME = 0
function say_msg(message, timeout)
    rb.splash(1, message)
    rb.sleep(timeout * rb.HZ)
end

function say_value(value,message,timeout)
  local message = string.format(message .. "%d", value)
  say_msg(message, timeout)
end

function cfg_num_setting(str_name)
local file = io.open(rb.ROCKBOX_DIR .. "/config.cfg", "r")
    if not file then
        return nil
    end
local value = nil
local contents = file:read("*all")

    i, j = string.find(contents, str_name .. ":")
    if j ~= nil then
        file:seek ("set", rb.atoi(j))
        value = file:read ("*num")
    end
    file:close() -- GC takes care of this if you would've forgotten it
    return value
end

function cfg_str_setting(str_name)
local file = io.open(rb.ROCKBOX_DIR .. "/config.cfg", "r")
    if not file then
        return nil
    end
local str = nil
local contents = file:read("*all")

    i, j = string.find(contents, str_name .. ":")
    if j ~= nil then
        file:seek ("set", rb.atoi(j))
        str = file:read ("*line")
        str = string.gsub(str, "%s", "")
    else
        str ="!"
    end
    file:close() -- GC takes care of this if you would've forgotten it
    return str
end

function ShowMainMenu() -- we invoke this function every time we want to display the main menu of the script
local s = 0
local mult = 1
local unit = " Minutes"


    while s == 0 or s == 5 do -- don't exit of program until user selects Exit
        if mult < 1 then
            mult = 1
            s = 0
        end
        mainmenu = {"More", mult * 1 .. unit, mult * 5 .. unit, mult * 10 .. unit, mult * 15 .. unit, "Less", "Exit"} -- define the items of the menu
        s = rb.do_menu("Reduce volume over", mainmenu, s, false) -- actually tell Rockbox to draw the menu

        -- In the line above: "Test" is the title of the menu, mainmenu is an array with the items
        -- of the menu, nil is a null value that needs to be there, and the last parameter is
        -- whether the theme should be drawn on the menu or not.
        -- the variable s will hold the index of the selected item on the menu.
        -- the index is zero based. This means that the first item is 0, the second one is 1, etc.
        if     s == 0 then mult = mult + 1
        elseif s == 1 then TIMEOUT = mult
        elseif s == 2 then TIMEOUT = mult * 5
        elseif s == 3 then TIMEOUT = mult * 10
        elseif s == 4 then TIMEOUT = mult * 15
        elseif s == 5 then mult = mult - 1 -- User selected to exit
        elseif s == 6 then os.exit() -- User selected to exit
        elseif s == -2 then os.exit() -- -2 index is returned from do_menu() when user presses the key to exit the menu (on iPods, it's the left key).
                                      -- In this case, user probably wants to exit (or go back to last menu).
        else rb.splash(2 * rb.HZ, "Error! Selected index: " .. s) -- something strange happened. The program shows this message when
                                                                  -- the selected item is not on the index from 0 to 3 (in this case), and displays
                                                                  -- the selected index. Having this type of error handling is not
                                                                  -- required, but it might be nice to have Especially while you're still
                                                                  -- developing the plugin.
        end
    end
end





ShowMainMenu()
rb.lcd_clear_display()
rb.lcd_update()

--if rb.strcasecmp(cfg_str_setting("keypress restarts sleeptimer"),"on") == 0 then
--say_msg("keypress restarts timer", 1)
--end

local volume = cfg_num_setting("volume")
    if volume == nil then
        volume = rb.sound_default(SOUND_VOLUME)
    end


local vol_min = rb.sound_min(SOUND_VOLUME)
local volsteps = -(vol_min - volume)
local seconds = (TIMEOUT * 60) / volsteps
local sec_left = (TIMEOUT * 60)
local hb = 0
local action = rb.get_action(rb.contexts.CONTEXT_STD, 0)
    if rb.pcm_is_playing() then
        while ((volume > vol_min) and (action ~= rb.actions.ACTION_STD_CANCEL)) do
            rb.lcd_clear_display()
            say_value(volume,sec_left .. " Sec, Volume: ", 1)
            local i = seconds * 2
            while ((i > 0) and (action ~= rb.actions.ACTION_STD_CANCEL)) do
                i = i - 1
                rb.lcd_drawline(hb, 1, hb, 1)
                rb.lcd_update()
                if hb >= rb.LCD_WIDTH then
                    hb = 0
                    rb.lcd_clear_display()
                    say_value(volume,sec_left .. " Sec, Volume: ", 1)
                end
                hb = hb + 1
                rb.sleep(rb.HZ / 2)
                action = rb.get_action(rb.contexts.CONTEXT_STD, 0)
                rb.yield()
            end
            volume = volume - 1
            rb.sound_set(SOUND_VOLUME, volume);
            sec_left = sec_left - seconds

        end
        rb.audio_stop()
        rb.lcd_clear_display()
        rb.lcd_update()

        say_msg("Playback Stopped", 5)
        os.exit()

    else
        rb.lcd_clear_display()
        rb.lcd_update()

        say_msg("Nothing is playing", 2)
        os.exit()
    end
Title: Re: more advanced sleep timer
Post by: tdb on December 28, 2016, 05:54:32 AM
Awesome, thanks a lot!

Searching the internet yesterday I found a similar solution someone made for itunes with applescript http://www.jonathanlaliberte.com/2010/03/15/itunes-sleep-timer-with-fade-out-applescript/

Tried your script and added the lua script to my Sleeptimer shortcuts. Works like a charm.

[shortcut]
name: Start Sleep Timer
type: time
data: sleep 25

[shortcut]
name: Stop Sleep Timer
type: time
data: sleep 0

[shortcut]
type: browse
data: /Slow_fade.lua
name: Slow Fade

Will experiment with the script and probably try to optimize the 'fade out curve' a bit to make it work best for me. I think I understand the most important parts of the script so it is very nice to be able to adjust it myself :)
Title: Re: more advanced sleep timer
Post by: Bilgus on December 28, 2016, 08:00:32 AM
No Problem, Once you get something awesome post it here for the next guy  :D
Title: Re: more advanced sleep timer
Post by: tdb on December 28, 2016, 05:37:17 PM
Hi Bilgus - I have tried the scripts on 3 of my Sansa Clip+ players and work fine but my old Sansa Clip hangs immediately after I start the script.
Has the first Clip some limitations the Clip+ doesn't have? Unfortunately it is the only Clip I have, so I am not able to check on another Clip. Not a big problem. I will also test the script on my e200's and C200 later (I got a bunch of Sansa players :) )

Updated them all to the latest dev build (b772782).

BTW - I am not planning on doing anything fancy with your script. Tested it today a couple of  times and I think the linear volume decrease is just working as gradually as I was hoping for. Maybe only change the default setting from 1 - 5 - 10 - 15 minutes to 10 - 15 - 20. 
Title: Re: more advanced sleep timer
Post by: Bilgus on December 28, 2016, 10:12:52 PM
I'll have to look into it might be that there isn't enough buffer free on the clip, also you can add a save file and then have your last setting as the first one that comes up
Title: Re: more advanced sleep timer
Post by: Bilgus on December 29, 2016, 03:44:34 PM
I don't even see the lua viewer on the clip you must have a V1 clip I see in the
defines that one only has a plugin buffer of 10000 where as most of the rest have one at 80000 or >

I'll make a real live fade to sleep plugin in the next few days
Title: Re: more advanced sleep timer
Post by: tdb on December 30, 2016, 06:08:24 PM
I don't even see the lua viewer on the clip you must have a V1 clip I see in the
defines that one only has a plugin buffer of 10000 where as most of the rest have one at 80000 or >

I'll make a real live fade to sleep plugin in the next few days

The Clip is a v2 so the buffer for that one might be too low as well?

Anyway, having the feature as a plugin would indeed be nice and might encourage more people to try it out. 
Lua script is working fine though, was sleeping like a baby last night ;) 
Title: Re: more advanced sleep timer
Post by: tdb on January 03, 2017, 03:50:23 PM
Tested the Lua script on my Pandora, no issues. Not working on my C200v2 though. Message 'can't open....'. I guess Lua isn't available for the C200 as well.
Will try it out on my E200 later.
Title: Re: more advanced sleep timer
Post by: tdb on January 05, 2017, 03:33:07 PM
Lua script works fine on my E200 as well.

The script seems to use the volume to start the fade timer with from the config.cfg file - is it possible with Lua to 'read' the actual playing volume?   
Title: Re: more advanced sleep timer
Post by: Bilgus on January 05, 2017, 06:12:54 PM
not that I have figured out as of yet -- Not saying there isn't just saying out of 5 or 6 ways I thought might work the cfg file worked the others not so much.
Title: Re: more advanced sleep timer
Post by: tdb on January 07, 2017, 04:47:12 AM
OK - np
I am going to change it to a fixed volume level then that is always comfortable to start with.

Title: Re: more advanced sleep timer
Post by: Bilgus on February 16, 2017, 05:45:33 PM
I've added the requisite functions to rb plugins with a commit on gerrit it will allow me to pull in the current volume level and the sleep timer seconds I'll post an updated script if and when it gets committed
Title: Re: more advanced sleep timer
Post by: tdb on February 17, 2017, 04:13:37 PM
Nice - that will improve an already very helpful script  :)

I have been using the script almost daily for more than a month now and it is working brilliantly. After falling asleep I didn't wake up during duration of the sleeptimer once. So it is doing in practice exactly what I was hoping for.

Would this be possible to script after the update: being able to reset the fade out- and sleep-timer while the script is running with a keypress (or two to prevent a key being accidentally pressed)? Especially useful when you notice you have problems getting to sleep and the previously set sleeptimer/fade out duration is not long enough. 
Title: Re: more advanced sleep timer
Post by: tdb on December 24, 2017, 07:57:20 AM
I've added the requisite functions to rb plugins with a commit on gerrit it will allow me to pull in the current volume level and the sleep timer seconds I'll post an updated script if and when it gets committed

Sorry for grave digging this thread.   
Noticed there are indeed some commits on gerrit, but they don't seem to be implemented yet. Still a chance we will see the feature in the dev builds?

Title: Re: more advanced sleep timer
Post by: Bilgus on December 24, 2017, 12:32:48 PM
I've got a plugin that implements it in progress but I still haven't finished it yet but I will eventually get interested in it again

Code: [Select]
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2017 William Wilgus
 *
 * 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"
#define settings_filename PLUGIN_APPS_DIR "/fade2sleep_settings.f2s"

#define MSTR_CT(x) (sizeof(x)/sizeof(0[x]))
#define MFMT_PLAYLIST       "%s %s %s;"
#define MFMT_TIME           "%s %s;"
#define MFMT_VOL            "%s %d %s;"
#define MFMT_STR            "%s;"
#define MFMT_SEPARATOR      " ;"
#define MENU_SETTINGS_BEGIN "",

#define MENUSETTING_VOLUME(item) setting_strings[item],settings->table[item],\
                                  rb->sound_unit(SOUND_VOLUME)
#define MENUSETTING_TRIGGER(item) setting_strings[item],\
                                   trigger_strings[settings->table[item]]
#define MENUSETTING_ACTION(item) action_strings[settings->table[item]]
#define MENUSETTING_PLAY(item)   setting_strings[item],\
                                   play_strings[settings->table[item]],\
                                   (settings->table[item])? settings->play : "\0"
#define WATCHED_EV(flag) ((watched_events & flag) == flag) ? flag : 0
#define THREAD_STACK_SIZE DEFAULT_STACK_SIZE
/* use long for aligning */
static unsigned long thread_stack[THREAD_STACK_SIZE/sizeof(long)];
#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 1)
#define TIMEOUT_VOL_CHANGE HZ/2
static unsigned int thread_id;
static struct event_queue thread_q SHAREDBSS_ATTR;
const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
//#define MAX_MENU_ENTRIES 10 /* menu string pointers statically allocated */
//const char* menu_strings[MAX_MENU_ENTRIES];// * sizeof(const char*)
static unsigned int triggered_events = 0;
static unsigned int watched_events = 0;
static bool quit = false, in_usb_mode = false;

/*Time setting formatting (NOT RTC)*/
#define MS_IN_TICK (1000U/HZ)
enum e_fmt_time_auto_idx
{
    UNIT_IDX_HR = 0,
    UNIT_IDX_MIN,
    UNIT_IDX_SEC,
    UNIT_IDX_MS,
    UNIT_IDX_TIME_COUNT,
};
/* format_time_auto */

#define UNIT_IDX_MASK       0x01FFU /*Return only Unit_IDX*/
#define UNIT_TRIM_ZERO      0x0200U /*Don't show leading zero on max_idx*/
#define UNIT_LOCK_HR        0x0400U /*Don't Auto Range below this field*/
#define UNIT_LOCK_MIN       0x0800U /*Don't Auto Range below this field*/
#define UNIT_LOCK_SEC       0x1000U /*Don't Auto Range below this field*/

static const char *unit_strings[] =
{
    [UNIT_INT] = "",    [UNIT_MS]  = "ms",
    [UNIT_SEC] = "s",   [UNIT_MIN] = "min",
    [UNIT_HOUR]= "hr",  [UNIT_KHZ] = "kHz",
    [UNIT_DB]  = "dB",  [UNIT_PERCENT] = "%",
    [UNIT_MAH] = "mAh", [UNIT_PIXEL] = "px",
    [UNIT_PER_SEC] = "per sec",
    [UNIT_HERTZ] = "Hz",
    [UNIT_MB]  = "MB",  [UNIT_KBIT]  = "kb/s",
    [UNIT_PM_TICK] = "units/10ms",
};
enum e_self_triggered_events
{   /* TRIGGERED WITHIN THIS PLUGIN */
    SELF_EVENT_CHECK_TRIGGERED  = 0,
    SELF_EVENT_TIMEOUT_REACHED  = 1,
    SELF_EVENT_FINALVOL_REACHED = 2,
};

enum e_triggered_events
{
    //TRIG_EVENT_=0x0001,
    TRIG_EVENT_START_PLAYBACK = 0x0002,
    TRIG_EVENT_TRACK_SKIP     = 0x0004,
    TRIG_EVENT_TRACK_CHANGE   = 0x0008,
    TRIG_EVENT_TRACK_FINISH   = 0x0010,
    TRIG_EVENT_VOL_CHANGED    = 0x0020,
    TRIG_EVENT_SCREEN_CHANGED = 0x0040,
    TRIG_EVENT_SCREEN_ON      = 0x0080,
    TRIG_EVENT_VOL_FINAL      = 0x0100,
    TRIG_EVENT_PAUSE          = 0x0200,
    TRIG_EVENT_RESUME         = 0x0400,
    TRIG_EVENT_PLAY           = 0x0800,
    TRIG_EVENT_STOP           = 0x1000,
    TRIG_EVENT_TIMEOUT        = 0x2000,
    //TRIG_EVENT_=0x4000,
    //TRIG_EVENT_=0x8000,
};
enum e_play_str
{
    PLAY_USECURRENT = 0,
    PLAY_LOAD,
};
static const char * play_strings[] =
{
    [PLAY_USECURRENT]"Use Current",
    [PLAY_LOAD]      "Load",
};

enum e_save_str
{
    SAV_SAVE = 0,
    SAV_SAVED,
};
static const char * saved_strings[] =
{
    [SAV_SAVE] "Save",
    [SAV_SAVED]"Saved",
};

enum e_trigger_str
{
    TRIG_NONE = 0,
    TRIG_TIMEOUT,
    TRIG_FINALVOL,
    TRIG_PAUSE,
    TRIG_VOLCHANGE,
    TRIG_TRACKCHANGE,
    TRIG_ANY,
};
static const char * trigger_strings[] =
{
    [TRIG_NONE]       "None",
    [TRIG_TIMEOUT]    "Timeout",
    [TRIG_FINALVOL]   "Final Volume",
    [TRIG_PAUSE]      "Pause",
    [TRIG_VOLCHANGE]  "Volume Change",
    [TRIG_TRACKCHANGE]"Track Change",
    [TRIG_ANY]        "Any Action",
};

enum e_delay_str
{
    DLY_NONE = 0,
    DLY_TIMEOUT,
    DLY_TRACKCHANGE,
    DLY_PAUSERESUME,
    DLY_PLAYSTOP,
};
static const char * delay_strings[] =
{
    [DLY_NONE]       "None",
    [DLY_TIMEOUT]    "Timeout",
    [DLY_TRACKCHANGE]"Track Change",
    [DLY_PAUSERESUME]"Pause/Resume",
    [DLY_PLAYSTOP]   "Play/Stop",
};

enum e_curvol_str
{
    CURVOL_NONE = 0,
    CURVOL_START,
    CURVOL_END,
};
static const char *  cur_vol_strings[] =
{
    [CURVOL_NONE] "None",
    [CURVOL_START]"Start",
    [CURVOL_END]  "End",
};

enum e_action_str
{
    ACT_NONE = 0,
    ACT_SLEEP,
    ACT_STARTOVER,
    ACT_INVERTFADE,
    ACT_EXIT,
};
static const char * action_strings[] =
{
    [ACT_NONE]      "None",
    [ACT_SLEEP]     "Sleep",
    [ACT_STARTOVER] "Start Over",
    [ACT_INVERTFADE]"Invert Fade",
    [ACT_EXIT]      "Exit",
};

enum e_ny_str
{
    NY_NO = 0,
    NY_YES,
};
static const char * ny_strings[] =
{
    [NY_NO] "No",
    [NY_YES]"Yes",
};

enum enum_menu
{ /*same order as they will be displayed in the menu*/
    M_PLAY = 0,
    M_DELAY,
    M_ACTDELAY,
    M_USECURVOL,
    M_STARTVOL,
    M_ENDVOL,
    M_TIMEOUT,
    M_TRIG1,
    M_ACT1,
    M_TRIG2,
    M_ACT2,
    M_TRIG3,
    M_ACT3,
    M_TSR,
    M_SEP,
    M_SAVE,
    M_RUN,
    M_EXIT,
    M_ITEM_COUNT,
};

static const char * setting_strings[M_ITEM_COUNT] =
{//[]"",
    [M_PLAY]  "Play",
    [M_DELAY]     "Delay",
    [M_ACTDELAY]  "After",
    [M_USECURVOL] "Use Cur Volume",
    [M_STARTVOL]  "Fade from :",
    [M_ENDVOL]    "to :",
    [M_TIMEOUT]   "Timeout",
    [M_TRIG1]     "After",
    [M_ACT1]      "Action1",
    [M_TRIG2]     "After",
    [M_ACT2]      "Action2",
    [M_TRIG3]     "After",
    [M_ACT3]      "Action3",
    [M_TSR]       "In Background",
    /*[M_SEP]*/
    [M_SAVE]      "Save",
    [M_RUN]       "Run",
    [M_EXIT]      "Exit",

};

static const int setting_icons[M_ITEM_COUNT] =
{//Icon_NOICON
[M_DELAY]Icon_Menu_setting,
[M_ACTDELAY]Icon_Playback_menu,
[M_USECURVOL]Icon_Questionmark,
[M_STARTVOL]Icon_EQ,
[M_ENDVOL]Icon_Audio,
[M_TIMEOUT]Icon_Config,
[M_TRIG1]Icon_Menu_setting,
[M_ACT1]Icon_Menu_functioncall,
[M_TRIG2]Icon_NOICON,
[M_ACT2]Icon_NOICON,
[M_TRIG3]Icon_NOICON,
[M_ACT3]Icon_NOICON,
[M_SEP]Icon_NOICON,
[M_SAVE]Icon_Submenu,
[M_EXIT]Icon_Reverse_Cursor,
};

//static int setting_table[M_ITEM_COUNT]={0};
static struct viewport viewport[NB_SCREENS];

static struct fade_settings
{
    int  vol_orig;
    int  vol_min;
    int  vol_max;
    int  vol_step;
    int  vol_cur;
    bool fade_out;
    long timeout_ticks;
    long ticks_remaining;

    int  audio_status;
    int  duration;
    int  delay_duration;
    int  vol_init;
    bool init_use_cur;
    int  vol_final;
    bool final_use_cur;
    int  actions;
    int  sleep;
    bool tsr;
} fade_set;

static struct settings
{
    uint32_t  crc;
    char      play[MAX_PATH];
    int       table[M_ITEM_COUNT];

} settings;

static const char * dyn_menu_get_entry(int selected_item, void * data,
                                       char * buffer, size_t buffer_len)
{
    //(void)data;
    (void)buffer;
    (void)buffer_len;
    char * menustr = ((char**) data)[selected_item];
    return menustr;
}

static enum themable_icons menu_get_icon(int selected_item, void * data)
{
    (void)data;
    if ((unsigned) selected_item >= M_ITEM_COUNT)
        return Icon_NOICON;
    return setting_icons[selected_item];//data[selected_item];// : Icon_NOICON;
}

static int do_dyn_menu(const char* title,
                       char* strentry, const char ** entrylist, int entries,
                       int *cur_selection, int *list_start,
                       list_get_icon *icon_callback,
                       struct viewport parent[NB_SCREENS], bool hide_theme)
{

/*
    Creates a menu, can use non constant strings by supplying a comma
    separated list of menu items via strentry ex. "item1;item2;item3;item4"
    strentry may be a buffer filled by any means (sprintf) or a string literal
    but NOT const since every separator ';' will be replaced by a '\0'
    ALTERNATIVELY can use a list of const strings via entrylist
    ex. static const char * menu_str[] ={"item1", "item2", "item3"};

do_dyn_menu(titlestr, menustr, entrylist, entries, start, hidetheme)
titlestr   - title for the menu
menustr    - comma separated list of menu entries
entrylist  - ALTERNATIVE to menustr pointer to const string list
entries    - number of entries in menustr if > MAX_MENU_ENTRIES will be truncated.
cur_sel    - sets menu item selected, returns users selection
list_start - sets menu entry at top of list, keeps position between calls
hide_theme - hides theme elements such as the status bar and background
returns selected item index, -2 if canceled, <0 on other error
*/
    enum{MAX_DYN_MENU_ENTRIES=M_ITEM_COUNT +1 };
    static const char* menu_strings[MAX_DYN_MENU_ENTRIES];
    char *remain, *token = NULL;
    const char * const sep= ";";

    struct gui_synclist list;
    int result = 0;
    int action;
    entries = MIN(MAX_DYN_MENU_ENTRIES, entries);
    if(menu_strings != NULL && entrylist == NULL)
    {
        for(int i=0; i < entries; i++)
        {
            token = rb->strtok_r(strentry, sep, &remain);
            strentry = NULL; /*strtok will continue with sequence*/
            menu_strings[i] = (token != NULL)? token : "???";
        }
        entrylist = menu_strings;
    }
   
    rb->gui_synclist_init(&list, dyn_menu_get_entry, entrylist, true, 1, parent);

    rb->gui_synclist_set_title(&list, (unsigned char*) title, Icon_Rockbox);
    if (icon_callback)
        rb->gui_synclist_set_icon_callback(&list, *icon_callback);
    rb->gui_synclist_set_nb_items(&list, entries);
    rb->gui_synclist_select_item(&list, (cur_selection) ? *cur_selection : 0);

    FOR_NB_SCREENS(i)
    {   
        if (list_start)
            list.start_item[i] = *list_start;
        rb->viewportmanager_theme_enable(i, !hide_theme, parent);
    }
 
    rb->gui_synclist_draw(&list);

    while (result == 0)
    {
        action = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
        if (rb->gui_synclist_do_button(&list, &action, LIST_WRAP_UNLESS_HELD))
            continue;


        switch (action)
        {
            case ACTION_STD_OK:
            {
                result = 1;
                break;
            }
            case ACTION_STD_CANCEL:
            {
                result = -1;
                break;
            }
        }
    }

    if (cur_selection)
        *cur_selection = rb->gui_synclist_get_sel_pos(&list);

    FOR_NB_SCREENS(i)
    {
        rb->viewportmanager_theme_undo(i, false);
        if (list_start)
        {
            if (i == 0)
                *list_start = 0;
            *list_start |= list.start_item[i];
        }   
    }


return result;
}

/*
unsigned int ms_to_ticks(unsigned int ms)
{
    return ms/MS_IN_TICK;
}

unsigned int ticks_to_ms(unsigned int ticks)
{
    return ticks * MS_IN_TICK;
}
*/
/*  time_split_units()
    split time values depending on base unit
    unit_idx: UNIT_HOUR, UNIT_MIN, UNIT_SEC, UNIT_MS
    abs_value: absolute time value
    units_in: array of unsigned ints with UNIT_IDX_TIME_COUNT fields
*/
static unsigned int time_split_units(int unit_idx, unsigned long abs_val,
                              unsigned int (*units_in)[UNIT_IDX_TIME_COUNT])
{
    unsigned int base_idx = UNIT_IDX_HR;
    int hours;
    int minutes  = 0;
    int seconds  = 0;
    int millisec = 0;

    switch (unit_idx & UNIT_IDX_MASK) /*Mask off upper bits*/
    {
            case UNIT_MS:
                base_idx = UNIT_IDX_MS;
                millisec = abs_val;
                abs_val  = abs_val  /  1000U;
                millisec = millisec - (1000U * abs_val);
                /* fallthrough and calculate the rest of the units */
            case UNIT_SEC:
                if (base_idx == UNIT_IDX_HR)
                    base_idx = UNIT_IDX_SEC;
                seconds  = abs_val;
                abs_val  = abs_val  / 60U;
                seconds  = seconds - (60U * abs_val);
                /* fallthrough and calculate the rest of the units */
            case UNIT_MIN:
                if (base_idx == UNIT_IDX_HR)
                    base_idx = UNIT_IDX_MIN;
                minutes  = abs_val;
                abs_val  = abs_val / 60U;
                minutes  = minutes -(60U * abs_val);
                /* fallthrough and calculate the rest of the units */
            case UNIT_HOUR:
            default:
                hours    = abs_val;
                break;
    }

    (*units_in)[UNIT_IDX_HR]  = hours;
    (*units_in)[UNIT_IDX_MIN] = minutes;
    (*units_in)[UNIT_IDX_SEC] = seconds;
    (*units_in)[UNIT_IDX_MS]  = millisec;

    return base_idx;
}

/* format_time_auto - return an auto ranged time string;
   buffer:  needs to be at least 64 characters

   unit_idx: specifies lowest or base index of the value
   add | UNIT_LOCK_ to prevent autorange below this index
   add | UNIT_TRIM_ZERO to supress leading zero on the largest unit

   value: should be passed in the same form as unit_idx

   supress_unit: if true unit string is NOT printed

   idx_pos[2]: (if !NULL) [0] specifies an index of interest,
   the offset and width for that index will be returned.
   In field [0] offset, field [1] length
   Ex: given 12:34:56.78 if you pass the idx_pos UNIT_IDX_MIN
   idx_pos returns -> {3,2}.. offset(3) and length(2) = '34'
*/
static const char *format_time_auto(char *buffer, int buf_len, const long value,
                                  int unit_idx, bool supress_unit,
                                  unsigned char (*idx_pos)[2])
{
    const char * const sign        = &"-"[value < 0 ? 0 : 1];   
    bool               is_rtl      = rb->lang_is_rtl();
    unsigned int       timebuf_len = 30; /* -2147483648:00:00.00\0 */
    char               *timebuf;/*timebuf[24]; shared with buffer instead*/
    int                len, left_offset;
    unsigned char      base_idx, max_idx;

    unsigned int       units_in[UNIT_IDX_TIME_COUNT];
    char               fwidth[UNIT_IDX_TIME_COUNT] =
                       {
                            [UNIT_IDX_HR]  = 0, /* hr is variable length */
                            [UNIT_IDX_MIN] = 2,
                            [UNIT_IDX_SEC] = 2,
                            [UNIT_IDX_MS]  = 3,
                       }; /* {0,2,2,3}; Field Widths*/
    unsigned char      offsets[UNIT_IDX_TIME_COUNT] =
                       {
                            [UNIT_IDX_HR]  = 10,/* ?:59:59.999 Std offsets */
                            [UNIT_IDX_MIN] = 7, /*0?:+1:+4.+7 need calculated */
                            [UNIT_IDX_SEC] = 4,/* 999.59:59:0  RTL offsets */
                            [UNIT_IDX_MS]  = 0,/* 0  .4 :7 :10 won't change */
                       }; /* {10,7,4,0}; Offsets*/
    static const int   unitlock[UNIT_IDX_TIME_COUNT] =
                       {
                            [UNIT_IDX_HR]  = UNIT_LOCK_HR,
                            [UNIT_IDX_MIN] = UNIT_LOCK_MIN,
                            [UNIT_IDX_SEC] = UNIT_LOCK_SEC,
                            [UNIT_IDX_MS]  = 0,
                       }; /* unitlock*/
    static const int   units[UNIT_IDX_TIME_COUNT] =
                       {
                            [UNIT_IDX_HR]  = UNIT_HOUR,
                            [UNIT_IDX_MIN] = UNIT_MIN,
                            [UNIT_IDX_SEC] = UNIT_SEC,
                            [UNIT_IDX_MS]  = UNIT_MS,
                       }; /* units*/

    buf_len = buf_len - (timebuf_len + 2);
    timebuf = &buffer[buf_len + 1]; /* use part of the supplied buffer */
    if (buf_len < 32)
        return buffer;

    if (idx_pos != NULL)
    {
        (*idx_pos)[0] = MIN((*idx_pos)[0], UNIT_IDX_TIME_COUNT - 1);
        unit_idx |= unitlock[(*idx_pos)[0]];
    }

    base_idx = time_split_units(unit_idx, abs(value), &units_in);

    if (units_in[UNIT_IDX_HR] || (unit_idx & unitlock[UNIT_IDX_HR]))
        max_idx = UNIT_IDX_HR;
    else if (units_in[UNIT_IDX_MIN] || (unit_idx & unitlock[UNIT_IDX_MIN]))
        max_idx = UNIT_IDX_MIN;
    else if (units_in[UNIT_IDX_SEC] || (unit_idx & unitlock[UNIT_IDX_SEC]))
        max_idx = UNIT_IDX_SEC;
    else if (units_in[UNIT_IDX_MS])
        max_idx = UNIT_IDX_MS;
    else /* value is 0*/
        max_idx = base_idx;

    if (!is_rtl)
    {
        len = snprintf(timebuf, timebuf_len,
                       "%02d:%02d:%02d.%03d",
                       units_in[UNIT_IDX_HR],
                       units_in[UNIT_IDX_MIN],
                       units_in[UNIT_IDX_SEC],
                       units_in[UNIT_IDX_MS]);

        fwidth[UNIT_IDX_HR]   = len - offsets[UNIT_IDX_HR];

        offsets[UNIT_IDX_MS]  = fwidth[UNIT_IDX_HR] + offsets[UNIT_IDX_MIN];
        offsets[UNIT_IDX_SEC] = fwidth[UNIT_IDX_HR] + offsets[UNIT_IDX_SEC];
        offsets[UNIT_IDX_MIN] = fwidth[UNIT_IDX_HR] + 1;
        offsets[UNIT_IDX_HR]  = 0;

        timebuf[offsets[base_idx] + fwidth[base_idx]] = '\0';

        left_offset  = -(offsets[max_idx]);
        left_offset += rb->strlcpy(buffer, sign, buf_len);

        /* trim leading zero on the max_idx */
        if ((unit_idx & UNIT_TRIM_ZERO) == UNIT_TRIM_ZERO &&
            timebuf[offsets[max_idx]] == '0')
        {
            offsets[max_idx]++;
        }

        rb->strlcat(buffer, &timebuf[offsets[max_idx]], buf_len);

        if (!supress_unit)
        {
            rb->strlcat(buffer, " ", buf_len);
            rb->strlcat(buffer, unit_strings[units[max_idx]], buf_len);
        }

    }
    else /*RTL Languages*/
    {
        len = snprintf(timebuf, timebuf_len,
                       "%03d.%02d:%02d:%02d",
                       units_in[UNIT_IDX_MS],
                       units_in[UNIT_IDX_SEC],
                       units_in[UNIT_IDX_MIN],
                       units_in[UNIT_IDX_HR]);

        fwidth[UNIT_IDX_HR] = len - offsets[UNIT_IDX_HR];

        left_offset = -(offsets[base_idx]);

        /* trim leading zero on the max_idx */
        if ((unit_idx & UNIT_TRIM_ZERO) == UNIT_TRIM_ZERO &&
            timebuf[offsets[max_idx]] == '0')
        {
            timebuf[offsets[max_idx]] = timebuf[offsets[max_idx]+1];
            fwidth[max_idx]--;
        }

        timebuf[offsets[max_idx] + fwidth[max_idx]] = '\0';

        if (!supress_unit)
        {
            rb->strlcpy(buffer, unit_strings[units[max_idx]], buf_len);
            left_offset += rb->strlcat(buffer, " ", buf_len);
            rb->strlcat(buffer, &timebuf[offsets[base_idx]], buf_len);
        }
        else
            rb->strlcpy(buffer, &timebuf[offsets[base_idx]], buf_len);

        rb->strlcat(buffer, sign, buf_len);
    }

    if (idx_pos != NULL)
    {
        (*idx_pos)[1]= fwidth[*(idx_pos)[0]];
        (*idx_pos)[0]= left_offset + offsets[(*idx_pos)[0]];
    }
    return buffer;
}


static void draw_horiz_scrollbar(struct screen * display, int top, int height, int cur_pct)
{
    rb->gui_scrollbar_draw(display,                  /* screen */
                           5,                /* x */
                           top,                    /* y */
                           LCD_WIDTH-10, /* width */
                           height ,              /* height */
                           100,                 /* items */
                           0,                  /* min_shown */
                           cur_pct,           /* max_shown */
                           HORIZONTAL);      /* flags */
}

static inline int get_button(void)
{
    return pluginlib_getaction(HZ/10, plugin_contexts,
                               ARRAYLEN(plugin_contexts));
}

static bool can_play(void)
{
    int audio_status = rb->audio_status();
    if ((!audio_status && rb->global_status->resume_index != -1)
        && (rb->playlist_resume() != -1)) {
        return true;
    }
    else if (audio_status & AUDIO_STATUS_PLAY)
        return true;

    return false;
}

static void resume_audio(bool from_stop)
{
    int audio_status = rb->audio_status();
    //rb->playlist_resume()
    if (from_stop && !audio_status && rb->global_status->resume_index != -1) {
        if (rb->playlist_resume() != -1) {
            rb->playlist_resume_track(rb->global_status->resume_index,
                rb->global_status->resume_crc32,
                rb->global_status->resume_elapsed,
                rb->global_status->resume_offset);
        }
    }
    else if (audio_status & AUDIO_STATUS_PLAY)
        rb->audio_resume();
}

static void pause_audio(void)
{
    if (rb->audio_status() & AUDIO_STATUS_PLAY)
        rb->audio_pause();
}

int value_to_pct(int value, int min, int max)
{
    int pct = 0;
    pct = ((value * 100 - min *100)/(abs(max-min)));

    return pct;
}

static int clamp_int(int value, int min, int max)
{
    int tmp = 0;
    if (min > max)
    { /* Swap max and min if needed */
        tmp = max;
        max = min;
        min = tmp;
    }

    if (value < min)
        value = min;
    else if (value > max)
        value = max;

    return value;
}

static int do_select_clamped(int v1, int v2, bool use_v2, int v_min, int v_max)
{
    /*
        selects val1 or val2 by bool use_v2
        false = val1, true = val2
        0     = val1  1    = val2
        returns selected clamped to v_min/v_max
    */
    int value;
    if (use_v2)
        value = v2;
    else
        value = v1;

    return clamp_int(value, v_min, v_max);
}

static int clamp_int_rollover(int value, int min, int max)
{
    int tmp = 0;
    if (min > max)
    { /* Swap max and min if needed */
        tmp = max;
        max = min;
        min = tmp;
    }

    if (value < min)
        value = max;
    else if (value > max)
        value = min;

    return value;
}

static int do_volume_change(int vol_step, int vol_min, int vol_max)
{
    /*
        Negative numbers lower volume
        Positive numbers raise volume
        clamps to vol_min/volmax
        returns current volume
    */
    rb->button_clear_queue(); //clear any pending buttons
    int vol = rb->sound_get_current(SOUND_VOLUME);

    vol = clamp_int(vol + vol_step, vol_min, vol_max);

    rb->sound_set_current(SOUND_VOLUME, vol);

    return vol;
}

static void do_set_fade(struct fade_settings * fade_set)
{

    int duration = fade_set->duration;
    int vol_min = rb->sound_min(SOUND_VOLUME);
    int vol_max = rb->sound_max(SOUND_VOLUME);
    int vol_orig = rb->sound_get_current(SOUND_VOLUME); /* get current volume */
    int vol_cur; /*vol_orig is the global volume not necessarily current volume*/
    int vol_init;
    int vol_final;
    int vol_step;
    int vol_range;
    bool fade_out;
    long ticks_remaining;
    long timeout_ticks;

    vol_init = do_select_clamped(vol_orig, fade_set->vol_init,
                                 !fade_set->init_use_cur, vol_min, vol_max);
    fade_set->vol_init = vol_init;
    vol_cur = vol_init;

    vol_final = do_select_clamped(vol_orig, fade_set->vol_final,
                                  !fade_set->final_use_cur, vol_min, vol_max);
    fade_set->vol_final = vol_final;

    if (vol_init > vol_final)
    {
        fade_out = true;
        vol_range = vol_init - vol_final; //abs()
        vol_step = -1;
    }
    else
    {
        fade_out = false;
        vol_range = vol_final - vol_init;//abs()
        vol_step = 1;
    }

    fade_set->fade_out = fade_out;

    fade_set->vol_step = vol_step;
    ticks_remaining = HZ * duration;
    if (ticks_remaining < HZ)
        ticks_remaining = HZ;

    fade_set->ticks_remaining = ticks_remaining;

    timeout_ticks = (ticks_remaining / vol_range);
    if (timeout_ticks < HZ/10)
        timeout_ticks = HZ/10;

    fade_set->timeout_ticks = timeout_ticks;

    fade_set->vol_min = vol_min;

    fade_set->vol_max = vol_max;

    fade_set->vol_cur = vol_cur;
    fade_set->vol_orig = vol_orig;
}

static bool do_fade(struct fade_settings * fade_set)
{
    int vol_min  = fade_set->vol_init;
    int vol_max  = fade_set->vol_final;
    int vol_step = fade_set->vol_step;
    int vol_cur  = fade_set->vol_cur;
    int ret = true;

    vol_cur = do_volume_change(vol_step, vol_min, vol_max);
    if (fade_set->vol_cur == vol_cur)
    {
        rb->splashf(HZ, "usr = %d", vol_cur);
    }
    if (vol_cur == vol_max)
    {
        ret = false;
        rb->splashf(HZ, "v = %d", vol_cur);
    }
    fade_set->vol_cur = vol_cur;
    return ret;
}

static int get_button_dpad(int *value, int *h_pos, int * v_pos, unsigned int multiplier)
{
/*  Allow user to use left,right, up, down, and (scroll) buttons to set a value
    single button press increments/decrements value by 1 * multiplier
    repeat button press accelerates inc/dec by multiplier * 2
    if h_pos is not NULL then left decrements h_pos, right increments
    if v_pos is not NULL then down decrements v_pos, up increments
    h_pos/v_pos being defined blocks actions for respective
    single press inc/dec of value
    Returns 0 if no button matched,
            -1 if canceled,
            1 if value selected
*/
    int button;
    int exit_val = 0;


        button = get_button();
        switch (button)
        {
            case PLA_RIGHT:
                if (h_pos) { *h_pos += 1; break; }
            case PLA_UP:
                if (v_pos) { *v_pos += 1; break; }
                *value+=1 * multiplier;
                break;
            case PLA_RIGHT_REPEAT:
            case PLA_UP_REPEAT:
                *value+=2 * multiplier;
                break;
#ifdef HAVE_SCROLLWHEEL
            case PLA_SCROLL_FWD:
                *value+=1 * multiplier;
                break;
            case PLA_SCROLL_FWD_REPEAT:
                *value+=2 * multiplier;
                break;
#endif
            case PLA_LEFT:
                if (h_pos) { *h_pos -= 1; break; }
            case PLA_DOWN:
                if (v_pos) { *v_pos -= 1; break; }
                *value-=1 * multiplier;
                break;
            case PLA_LEFT_REPEAT:
            case PLA_DOWN_REPEAT:
                *value-=2 * multiplier;
                break;
#ifdef HAVE_SCROLLWHEEL
            case PLA_SCROLL_BACK:
                *value-=1 * multiplier;
                break;
            case PLA_SCROLL_BACK_REPEAT:
                *value -=2 * multiplier;
                break;
#endif
            case PLA_SELECT:
            case PLA_SELECT_REPEAT:
                exit_val = 1;
                break;
            case PLA_EXIT:
            case PLA_CANCEL:
                exit_val  = -1;
                break;

            default:
                exit_val  = 0;
                break;
    }


    return exit_val;
}
void pop_up_frame(struct screen * display, int x, int y, int width, int height)
{
/*
|___________|
|           |
|           |
|___________|
*/

    //int title_height = display->getcharheight() + 5;
    //display->puts_scroll(1, 0, title_buf);
//void (*lcd_fillrect)(int x, int y, int width, int height);
    display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
    display->fillrect(x, y, width, height);
    display->set_drawmode(DRMODE_SOLID);
    display->hline(0, width, height/3);
    display->hline(1, width-1, height);
    display->vline(x, 0, height-1);
    display->vline(width-x, 0, height-1);
   

}
int do_set_value(const char * title, int val, int val_min, int val_max, const char * val_lbl)
{
    struct screen * display = rb->screens[SCREEN_MAIN];
    int title_height = display->getcharheight() + 5;
    char title_buf[48]= "\0";
    int initial_val = val;
    int ret = 0;

    FOR_NB_SCREENS(i)   
        rb->viewportmanager_theme_enable(i, false, NULL);
    //rb->lcd_clear_display();
        pop_up_frame(display, 0, 0, LCD_WIDTH-1, title_height * 3);
    while(ret == 0)
    {
        ret = get_button_dpad(&val, NULL, NULL, 1); /* allow user to set value*/

        val = clamp_int_rollover(val, val_min, val_max);

        draw_horiz_scrollbar(display, title_height * 2, title_height/2,
                             value_to_pct(val, val_min, val_max));

        rb->snprintf(title_buf, sizeof(title_buf), "%s %d %s   ",
                                                   title, val, val_lbl);

        display->puts_scroll(1, 0, title_buf);
        display->update();
        rb->sleep(HZ/100);
    }
    if (ret == -1)
    {
        val = initial_val;
        rb->splash(HZ/2, "Canceled");
    }
    display->clear_display();
    FOR_NB_SCREENS(i)
        rb->viewportmanager_theme_undo(i, true);
   
    //display->update();
    return val;
}

int do_set_time(const char * title, int sec, int sec_min, int sec_max)
{
    char time_buf[64]= "\0";
    const int time_buf_sz = sizeof(time_buf);
    struct screen *display = rb->screens[SCREEN_MAIN];

    int title_height = display->getcharheight() + 5;
    int is_rtl = rb->lang_is_rtl();

    const int initial_sec = sec; /* on cancel original time returned*/
    int ret = 0;

    enum {FIELD_ST = 0, FIELD_CT = 3};
    unsigned char idx_pos[2];
    int field_pos = (is_rtl? FIELD_ST : FIELD_CT - 1);
    int rel_field; /* If lang is RTL then fields/cols are reversed */

    static const unsigned char field_cols[FIELD_CT] =
                               {UNIT_IDX_HR, UNIT_IDX_MIN, UNIT_IDX_SEC};

    static const unsigned int  multiplier_cols[FIELD_CT] = {3600, 60, 1};

    int offset = 1;
    const int lcd_chr_width = (LCD_WIDTH/rb->lcd_getstringsize("W", NULL, NULL));

    //rb->lcd_clear_display();

    pop_up_frame(display, 0, 0, LCD_WIDTH-1, title_height * 3);
    while(ret == 0)
    {

        field_pos = clamp_int_rollover(field_pos, FIELD_ST, FIELD_CT - 1);
        /* if lang is RTL then columns flip */
        rel_field = (is_rtl? FIELD_CT - field_pos - 1 : field_pos);

        idx_pos[0] = field_cols[rel_field];

        ret = get_button_dpad(&sec, &field_pos, NULL, multiplier_cols[rel_field]);
        /* allow user to set value*/

        sec = clamp_int_rollover(sec, sec_min, sec_max);

        rb->lcd_puts_scroll(offset, 2, "             ");

        format_time_auto(time_buf, time_buf_sz, sec, UNIT_SEC, false, &idx_pos);

        offset = (lcd_chr_width - rb->strlen(time_buf))/2;
        rb->lcd_puts_scroll(offset, 2, time_buf);

        rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
        time_buf[idx_pos[0] + idx_pos[1]] = '\0';
        /*draw selected position inverse*/
        rb->lcd_puts_scroll(offset + idx_pos[0], 2, &time_buf[idx_pos[0]]);
        rb->lcd_set_drawmode(DRMODE_SOLID);

        rb->lcd_puts_scroll(1,0,title);
        rb->lcd_update();
        rb->sleep(HZ/100);
    }
    if (ret == -1)
    {
        sec = initial_sec;
        rb->splash(HZ/2, "Canceled");
    }
    rb->lcd_clear_display();
    rb->lcd_update();

    return sec;
}

uint32_t crc_settings(struct settings * set)
{
    //struct settings set_tmp

    /* some fields are ignored in crc calculation */
    uint32_t current_crc = set->crc;
    set->crc = 0;

    int vol_start = set->table[M_STARTVOL];
    int vol_end = set->table[M_ENDVOL];

    if (set->table[M_USECURVOL] == CURVOL_START)
        set->table[M_STARTVOL] = 0;
    if (set->table[M_USECURVOL] == CURVOL_END)
        set->table[M_ENDVOL] = 0;
   
    set->table[M_SAVE] = 0;

    uint32_t crc = rb->crc_32(set, sizeof(*set), 0xffffffff);

    /* put back ignored fields */
    set->crc = current_crc;
    set->table[M_STARTVOL] = vol_start;
    set->table[M_ENDVOL] = vol_end;
    return crc;
}

void load_settings(struct settings * settings, char* filename)
{
    int fh = rb->open(filename, O_RDONLY);
    struct settings settings_load;

    /* defaults */
    settings->play[0] = '\0';

    if(fh >= 0)
    { /* does file exist? */
        /* basic consistency check */
        if(rb->filesize(fh) == sizeof(*settings))
        {
            rb->read(fh, &settings_load, sizeof(struct settings));
            if (settings_load.crc == crc_settings(&settings_load))
            {
                rb->memcpy(settings, &settings_load, sizeof(struct settings));
            }
            else
                rb->splashf(HZ * 5, "CRC Check Failed %s", filename);
        }
        else
            rb->splashf(HZ * 5, "Incorrect File Size %s", filename);
        rb->close(fh);
    }

}

void save_settings(struct settings * settings, char* filename)
{
    int fd = rb->creat(filename, 0666);
    settings->crc = crc_settings(settings);
    if(fd >= 0)
    { /* does file exist? */
        rb->write (fd, settings, sizeof(*settings));
        rb->close(fd);
    }
}


void scroll_table_rollover(int * p_sel, int entries, int direction)
{
    int sel = *p_sel;
    if (direction < 0)
        (sel)--;
    else
        (sel)++;

    *p_sel = clamp_int_rollover(sel, 0, entries - 1);
}

static bool play_load(const char* filename)
{
    struct browse_context browse;
    if (rb->file_exists(filename))
    {
        rb->browse_context_init(&browse, 0, BROWSE_RUNFILE, "", 0, filename, "");
        rb->rockbox_browse(&browse);
        return true;
    }
    else
        rb->splashf(HZ * 2, "Error: %s Does Not Exist", filename);
    return false;
}

static bool play_picker(struct settings *settings)
{
    bool ret = false;
    struct browse_context browse;
    rb->browse_context_init(&browse, SHOW_PLAYLIST,
                            BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU,
                            "Play", Icon_Menu_setting, "/", "");

    browse.buf = settings->play;
    browse.bufsize = sizeof(settings->play);

    rb->rockbox_browse(&browse);

    if (browse.flags & BROWSE_SELECTED)
    {
        rb->splashf(HZ * 2, "%s", settings->play);
        if (rb->file_exists(settings->play))
            ret = true;
        //play_load("/test.txt");
    }
    return ret;
}

static void do_set_volume_table_values(struct settings *set)
{
/* user can elect to use the current volume level as the start or end volume */
    int vol = rb->sound_get_current(SOUND_VOLUME);
    int vol_min = rb->sound_min(SOUND_VOLUME);
    int vol_max = rb->sound_max(SOUND_VOLUME);
    set->table[M_STARTVOL] = do_select_clamped(set->table[M_STARTVOL], vol,
                                        set->table[M_USECURVOL] == CURVOL_START,
                                                               vol_min, vol_max);

    set->table[M_ENDVOL] = do_select_clamped(set->table[M_ENDVOL], vol,
                                           set->table[M_USECURVOL] == CURVOL_END,
                                                                vol_min, vol_max);
}

static void do_set_action_table_values(struct settings *set)
{
    if (set->table[M_TRIG1] == TRIG_NONE)
        set->table[M_ACT1] = ACT_NONE;
    if (set->table[M_TRIG2] == TRIG_NONE)
        set->table[M_ACT2] = ACT_NONE;
    if (set->table[M_TRIG3] == TRIG_NONE)
        set->table[M_ACT3] = ACT_NONE;
}

static void init_table_settings(struct settings *set)
{
    do_set_volume_table_values(set);
    do_set_action_table_values(set);

    set->table[M_SAVE] = (set->crc == crc_settings(set))? SAV_SAVED : SAV_SAVE;
}

static void event_callback(unsigned short id, void *param)
{

    static int last_audio = AUDIO_STATUS_ERROR;
    static int last_screen = 0;
    static bool backlight_on = true;
    int audio_status;
    int screen;
    int last_vol_change;
    if (((struct track_event *)param)->flags & TEF_REWIND)//TEF_AUTO_SKIP
        return; /* Not a true track end */

    switch(id)
    {
        case PLAYBACK_EVENT_START_PLAYBACK:
            triggered_events |= WATCHED_EV(TRIG_EVENT_START_PLAYBACK);
            break;
        case PLAYBACK_EVENT_TRACK_SKIP:
            if (!(((struct track_event *)param)->flags & TEF_AUTO_SKIP))
                triggered_events |= WATCHED_EV(TRIG_EVENT_TRACK_SKIP);
            break;
        case PLAYBACK_EVENT_TRACK_CHANGE:
            triggered_events |= WATCHED_EV(TRIG_EVENT_TRACK_CHANGE);
            break;
        case PLAYBACK_EVENT_TRACK_FINISH:
            if (!(((struct track_event *)param)->flags & TEF_REWIND))//TEF_AUTO_SKIP
                triggered_events |= WATCHED_EV(TRIG_EVENT_TRACK_FINISH);
            break;
        case SELF_EVENT_FINALVOL_REACHED:
            triggered_events |= WATCHED_EV(TRIG_EVENT_VOL_FINAL);
            break;
        case SELF_EVENT_TIMEOUT_REACHED:
            triggered_events |= WATCHED_EV(TRIG_EVENT_TIMEOUT);
            break;
        case SELF_EVENT_CHECK_TRIGGERED:
            last_vol_change = rb->global_status->last_volume_change;
            if (last_vol_change + TIMEOUT_VOL_CHANGE < *rb->current_tick)
                 triggered_events |= WATCHED_EV(TRIG_EVENT_VOL_CHANGED
Title: Re: more advanced sleep timer
Post by: tdb on December 25, 2017, 07:33:02 AM
Okay, thanks for your answer - there is no rush (it is not a bug and with the lua script there is already something that works good enough).

Still it is great to see new developments, not everyone is reading the forum so it would be nice to have it out of the box especially for these users.
As mentioned before script is working fine for me (and continuous to be incredibly helpful)

I was just curious about the status - maybe I will celebrate existence of the script with a yearly  friendly status update request ;)   



   


Title: Re: more advanced sleep timer
Post by: Bilgus on August 01, 2019, 11:41:41 PM
fade to sleep plugin can now be found in plugins/demos/lua_scripts
Title: Re: more advanced sleep timer
Post by: tdb on November 10, 2019, 11:15:22 AM
Haven't checked this thread for a while, but nice to see the script included. Still using it - almost daily.
Title: Re: more advanced sleep timer
Post by: Bilgus on November 10, 2019, 01:45:10 PM
I'm pretty sure I edited it to include the new sleep timer functionality and ability to read the starting volume level
Title: Re: more advanced sleep timer
Post by: tdb on November 24, 2019, 04:20:48 AM
Indeed you did  :) works nicely. That makes it perfect! Thanks. 

For easy access I added the previous script to my shortcuts - that doesn't seem to work with this script/version. Updated yesterday to the latest dev version.

tried:

[shortcut]
type: browse
data: /rocks/demos/lua_scripts/fade2sleep.lua
name: fade2sleep

copied fade2sleep.lua to root

[shortcut]
type: browse
data: /fade2sleep.lua
name: fade2sleep

and tried data type (also some other variants - all resulted in 'no file')

[shortcut]
type: data
data: fade2sleep
name: fade2sleep




Title: Re: more advanced sleep timer
Post by: tdb on December 13, 2019, 08:14:15 AM
@Bilgus, maybe you missed the 'question' part of my reply? Would be nice to have easier access to the script via a shortcut. Is that possible?
Title: Re: more advanced sleep timer
Post by: Bilgus on December 13, 2019, 08:41:23 AM
I don't think lua scripts work through shortcuts you might try adding the lua_scripts.lua script to shortcuts and see if that works
Title: Re: more advanced sleep timer
Post by: tdb on December 13, 2019, 10:54:02 AM
I don't think lua scripts work through shortcuts you might try adding the lua_scripts.lua script to shortcuts and see if that works

Thx. Will give that a try then. BTW Your previous script worked fine through a shortcut.