summaryrefslogtreecommitdiffstats
path: root/apps/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins')
-rw-r--r--apps/plugins/CATEGORIES3
-rw-r--r--apps/plugins/SOURCES8
-rw-r--r--apps/plugins/announce_status.c21
-rw-r--r--apps/plugins/bitmaps/native/SOURCES4
-rw-r--r--apps/plugins/bitmaps/native/_2048_tiles.26x26x2.bmpbin0 -> 6254 bytes
-rw-r--r--apps/plugins/db_folder_select.c652
-rw-r--r--apps/plugins/keybox.c2
-rw-r--r--apps/plugins/lib/SOURCES2
-rw-r--r--apps/plugins/lib/action_helper.c1
-rw-r--r--apps/plugins/lib/action_helper.h34
-rwxr-xr-xapps/plugins/lib/action_helper.pl209
-rw-r--r--apps/plugins/lib/arg_helper.c4
-rw-r--r--apps/plugins/lib/arg_helper.h2
-rw-r--r--apps/plugins/lib/button_helper.c1
-rw-r--r--apps/plugins/lib/button_helper.h38
-rwxr-xr-xapps/plugins/lib/button_helper.pl98
-rw-r--r--apps/plugins/lrcplayer.c4
-rwxr-xr-xapps/plugins/lua/rbdefines_helper.pl1
-rw-r--r--apps/plugins/lua/rocklua.c12
-rw-r--r--apps/plugins/lua_scripts/dbgettags.lua15
-rw-r--r--apps/plugins/lua_scripts/random_playlist.lua464
-rw-r--r--apps/plugins/lua_scripts/return2WPS.lua19
-rw-r--r--apps/plugins/main_menu_config.c2
-rw-r--r--apps/plugins/open_plugins.c45
-rw-r--r--apps/plugins/pictureflow/pictureflow.c352
-rw-r--r--apps/plugins/pitch_screen.c1279
-rw-r--r--apps/plugins/plugins.make29
-rw-r--r--apps/plugins/random_folder_advance_config.c50
-rw-r--r--apps/plugins/rb_info.c494
29 files changed, 3737 insertions, 108 deletions
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index d3093689f9..bb0960f501 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -22,6 +22,7 @@ clock,apps
codebuster,games
credits,viewers
cube,demos
+db_folder_select,viewers
demystify,demos
dice,games
dict,apps
@@ -76,6 +77,7 @@ pegbox,games
periodic_table,apps
pictureflow,demos
pitch_detector,apps
+pitch_screen,viewers
pixel-painter,games
plasma,demos
png,viewers
@@ -85,6 +87,7 @@ ppm,viewers
properties,viewers
quake,games
random_folder_advance_config,apps
+rb_info,demos
remote_control,apps
resistor,apps
reversi,games
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 910ffe4161..ab77dcde58 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -2,6 +2,9 @@
#if !defined(SIMULATOR) && (CONFIG_BATTERY_MEASURE != 0)
battery_bench.c
#endif
+#ifdef HAVE_TAGCACHE
+db_folder_select.c
+#endif
chessclock.c
credits.c
cube.c
@@ -14,6 +17,7 @@ mosaique.c
main_menu_config.c
properties.c
random_folder_advance_config.c
+rb_info.c
rockblox.c
search.c
settings_dumper.c
@@ -48,6 +52,10 @@ lamp.c
pitch_detector.c
#endif
+#ifdef HAVE_PITCHCONTROL
+pitch_screen.c
+#endif
+
mp3_encoder.c
wav2wv.c
diff --git a/apps/plugins/announce_status.c b/apps/plugins/announce_status.c
index a9958f198d..77e9015000 100644
--- a/apps/plugins/announce_status.c
+++ b/apps/plugins/announce_status.c
@@ -191,6 +191,8 @@ void announce(void)
rb->talk_force_shutup();
rb->sleep(HZ / 2);
voice_general_info(false);
+ if (rb->talk_id(VOICE_PAUSE, true) < 0)
+ rb->beep_play(800, 100, 1000);
//rb->talk_force_enqueue_next();
}
@@ -412,6 +414,7 @@ static int settings_menu(void)
/****************** main thread + helper ******************/
void thread(void)
{
+ bool in_usb = false;
long interval;
long last_tick = *rb->current_tick; /* for 1 sec tick */
@@ -424,6 +427,14 @@ void thread(void)
{
case SYS_USB_CONNECTED:
rb->usb_acknowledge(SYS_USB_CONNECTED_ACK);
+ in_usb = true;
+ break;
+ case SYS_USB_DISCONNECTED:
+ in_usb = false;
+ /*fall through*/
+ case EV_STARTUP:
+ rb->beep_play(1500, 100, 1000);
+ break;
case EV_EXIT:
return;
case EV_OTHINSTANCE:
@@ -431,15 +442,12 @@ void thread(void)
{
last_tick += interval;
rb->sleep(HZ / 10);
- announce();
+ if (!in_usb) announce();
}
break;
- case EV_STARTUP:
- rb->beep_play(1500, 100, 1000);
- break;
case EV_TRACKCHANGE:
rb->sleep(HZ / 10);
- announce();
+ if (!in_usb) announce();
break;
}
}
@@ -464,6 +472,7 @@ void thread_quit(void)
rb->thread_wait(gThread.id);
/* we don't want any more events */
rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, playback_event_callback);
+
/* remove the thread's queue from the broadcast list */
rb->queue_delete(&gThread.queue);
gThread.exiting = true;
@@ -560,6 +569,8 @@ int plugin_main(const void* parameter)
enum plugin_status plugin_start(const void* parameter)
{
/* now go ahead and have fun! */
+ if (rb->usb_inserted() == true)
+ return PLUGIN_USB_CONNECTED;
int ret = plugin_main(parameter);
return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR;
}
diff --git a/apps/plugins/bitmaps/native/SOURCES b/apps/plugins/bitmaps/native/SOURCES
index 37d0060213..f207f358b2 100644
--- a/apps/plugins/bitmaps/native/SOURCES
+++ b/apps/plugins/bitmaps/native/SOURCES
@@ -8,7 +8,11 @@ _2048_background.224x224x24.bmp
_2048_tiles.36x36x24.bmp
_2048_background.168x168x24.bmp
#elif MIN(LCD_WIDTH, LCD_HEIGHT)>=132 || MIN(LCD_WIDTH, LCD_HEIGHT)>=128
+#if (LCD_DEPTH > 2)
_2048_tiles.26x26x24.bmp
+#else
+_2048_tiles.26x26x2.bmp
+#endif
_2048_background.121x121x24.bmp
#elif MIN(LCD_WIDTH, LCD_HEIGHT)>=110
_2048_tiles.22x22x24.bmp
diff --git a/apps/plugins/bitmaps/native/_2048_tiles.26x26x2.bmp b/apps/plugins/bitmaps/native/_2048_tiles.26x26x2.bmp
new file mode 100644
index 0000000000..4b764c3e14
--- /dev/null
+++ b/apps/plugins/bitmaps/native/_2048_tiles.26x26x2.bmp
Binary files differ
diff --git a/apps/plugins/db_folder_select.c b/apps/plugins/db_folder_select.c
new file mode 100644
index 0000000000..7f51e520cb
--- /dev/null
+++ b/apps/plugins/db_folder_select.c
@@ -0,0 +1,652 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ *
+ * Copyright (C) 2012 Jonathan Gordon
+ * Copyright (C) 2012 Thomas Martitz
+* * Copyright (C) 2021 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"
+#ifdef ROCKBOX_HAS_LOGF
+#define logf rb->logf
+#else
+#define logf(...) do { } while(0)
+#endif
+
+/*
+ * Order for changing child states:
+ * 1) expand folder (skip to 3 if empty, skip to 4 if cannot be opened)
+ * 2) collapse and select
+ * 3) unselect (skip to 1)
+ * 4) do nothing
+ */
+
+enum child_state {
+ EXPANDED,
+ SELECTED,
+ COLLAPSED,
+ EACCESS,
+};
+
+struct child {
+ char* name;
+ struct folder *folder;
+ enum child_state state;
+};
+
+struct folder {
+ char *name;
+ struct child *children;
+ struct folder* previous;
+ uint16_t children_count;
+ uint16_t depth;
+};
+
+static char *buffer_front, *buffer_end;
+
+static struct
+{
+ int32_t len; /* keeps count versus maxlen to give buffer full notification */
+ uint32_t val; /* hash of all selected items */
+ char buf[3];/* address used as identifier -- only \0 written to it */
+ char maxlen_exceeded; /*0,1*/
+} hashed;
+
+static inline void get_hash(const char *key, uint32_t *hash, int len)
+{
+ *hash = rb->crc_32(key, len, *hash);
+}
+
+static char* folder_alloc(size_t size)
+{
+ char* retval;
+ /* 32-bit aligned */
+ size = ALIGN_UP(size, 4);
+ if (buffer_front + size > buffer_end)
+ {
+ return NULL;
+ }
+ retval = buffer_front;
+ buffer_front += size;
+ return retval;
+}
+
+static char* folder_alloc_from_end(size_t size)
+{
+ if (buffer_end - size < buffer_front)
+ {
+ return NULL;
+ }
+ buffer_end -= size;
+ return buffer_end;
+}
+#if 0
+/* returns the buffer size required to store the path + \0 */
+static int get_full_pathsz(struct folder *start)
+{
+ int reql = 0;
+ struct folder *next = start;
+ do
+ {
+ reql += rb->strlen(next->name) + 1;
+ } while ((next = next->previous));
+
+ if (start->name[0] != '/') reql--;
+ if (--reql < 0) reql = 0;
+ return reql;
+}
+#endif
+
+static size_t get_full_path(struct folder *start, char *dst, size_t dst_sz)
+{
+ size_t pos = 0;
+ struct folder *prev, *cur = NULL, *next = start;
+ dst[0] = '\0'; /* for rb->strlcat to do its thing */
+ /* First traversal R->L mutate nodes->previous to point at child */
+ while (next->previous != NULL) /* stop at the root */
+ {
+#define PATHMUTATE() \
+ ({ \
+ prev = cur; \
+ cur = next; \
+ next = cur->previous;\
+ cur->previous = prev; \
+ })
+ PATHMUTATE();
+ }
+ /*swap the next and cur nodes to reverse direction */
+ prev = next;
+ next = cur;
+ cur = prev;
+ /* Second traversal L->R mutate nodes->previous to point back at parent
+ * copy strings to buf as they go by */
+ while (next != NULL)
+ {
+ PATHMUTATE();
+ pos = rb->strlcat(dst, cur->name, dst_sz);
+ /* do not append slash to paths starting with slash */
+ if (cur->name[0] != '/')
+ pos = rb->strlcat(dst, "/", dst_sz);
+ }
+ logf("get_full_path: (%d)[%s]", (int)pos, dst);
+ return pos;
+#undef PATHMUTATE
+}
+
+/* support function for rb->qsort() */
+static int compare(const void* p1, const void* p2)
+{
+ struct child *left = (struct child*)p1;
+ struct child *right = (struct child*)p2;
+ return rb->strcasecmp(left->name, right->name);
+}
+
+static struct folder* load_folder(struct folder* parent, char *folder)
+{
+ DIR *dir;
+ char fullpath[MAX_PATH];
+
+ struct dirent *entry;
+ int child_count = 0;
+ char *first_child = NULL;
+ size_t len = 0;
+
+ struct folder* this = (struct folder*)folder_alloc(sizeof(struct folder));
+ if (this == NULL)
+ goto fail;
+
+ if (parent)
+ {
+ len = get_full_path(parent, fullpath, sizeof(fullpath));
+ if (len >= sizeof(fullpath))
+ goto fail;
+ }
+ rb->strlcpy(&fullpath[len], folder, sizeof(fullpath) - len);
+ logf("load_folder: [%s]", fullpath);
+
+ dir = rb->opendir(fullpath);
+ if (dir == NULL)
+ goto fail;
+ this->previous = parent;
+ this->name = folder;
+ this->children = NULL;
+ this->children_count = 0;
+ if (parent)
+ this->depth = parent->depth + 1;
+
+ while ((entry = rb->readdir(dir))) {
+ /* skip anything not a directory */
+ if ((rb->dir_get_info(dir, entry).attribute & ATTR_DIRECTORY) == 0) {
+ continue;
+ }
+ /* skip . and .. */
+ char *dn = entry->d_name;
+ if ((dn[0] == '.') && (dn[1] == '\0' || (dn[1] == '.' && dn[2] == '\0')))
+ continue;
+ /* copy entry name to end of buffer, save pointer */
+ int len = rb->strlen((char *)entry->d_name);
+ char *name = folder_alloc_from_end(len+1); /*for NULL*/
+ if (name == NULL)
+ {
+ rb->closedir(dir);
+ goto fail;
+ }
+ memcpy(name, (char *)entry->d_name, len+1);
+ child_count++;
+ first_child = name;
+ }
+ rb->closedir(dir);
+ /* now put the names in the array */
+ this->children = (struct child*)folder_alloc(sizeof(struct child) * child_count);
+
+ if (this->children == NULL)
+ goto fail;
+
+ while (child_count)
+ {
+ struct child *child = &this->children[this->children_count++];
+ child->name = first_child;
+ child->folder = NULL;
+ child->state = COLLAPSED;
+ while(*first_child++ != '\0'){};/* move to next name entry */
+ child_count--;
+ }
+ rb->qsort(this->children, this->children_count, sizeof(struct child), compare);
+
+ return this;
+fail:
+ return NULL;
+}
+
+struct folder* load_root(void)
+{
+ static struct child root_child;
+ /* reset the root for each call */
+ root_child.name = "/";
+ root_child.folder = NULL;
+ root_child.state = COLLAPSED;
+
+ static struct folder root = {
+ .name = "",
+ .children = &root_child,
+ .children_count = 1,
+ .depth = 0,
+ .previous = NULL,
+ };
+
+ return &root;
+}
+
+static int count_items(struct folder *start)
+{
+ int count = 0;
+ int i;
+
+ for (i=0; i<start->children_count; i++)
+ {
+ struct child *foo = &start->children[i];
+ if (foo->state == EXPANDED)
+ count += count_items(foo->folder);
+ count++;
+ }
+ return count;
+}
+
+static struct child* find_index(struct folder *start, int index, struct folder **parent)
+{
+ int i = 0;
+ *parent = NULL;
+
+ while (i < start->children_count)
+ {
+ struct child *foo = &start->children[i];
+ if (i == index)
+ {
+ *parent = start;
+ return foo;
+ }
+ i++;
+ if (foo->state == EXPANDED)
+ {
+ struct child *bar = find_index(foo->folder, index - i, parent);
+ if (bar)
+ {
+ return bar;
+ }
+ index -= count_items(foo->folder);
+ }
+ }
+ return NULL;
+}
+
+static const char * folder_get_name(int selected_item, void * data,
+ char * buffer, size_t buffer_len)
+{
+ struct folder *root = (struct folder*)data;
+ struct folder *parent;
+ struct child *this = find_index(root, selected_item , &parent);
+
+ char *buf = buffer;
+ if ((int)buffer_len > parent->depth)
+ {
+ int i = parent->depth;
+ while(--i > 0) /* don't indent the parent /folders */
+ *buf++ = '\t';
+ }
+ *buf = '\0';
+ rb->strlcat(buffer, this->name, buffer_len);
+
+ if (this->state == EACCESS)
+ { /* append error message to the entry if unaccessible */
+ size_t len = rb->strlcat(buffer, " ( ", buffer_len);
+ if (buffer_len > len)
+ {
+ rb->snprintf(&buffer[len], buffer_len - len, rb->str(LANG_READ_FAILED), ")");
+ }
+ }
+
+ return buffer;
+}
+
+static enum themable_icons folder_get_icon(int selected_item, void * data)
+{
+ struct folder *root = (struct folder*)data;
+ struct folder *parent;
+ struct child *this = find_index(root, selected_item, &parent);
+
+ switch (this->state)
+ {
+ case SELECTED:
+ return Icon_Cursor;
+ case COLLAPSED:
+ return Icon_Folder;
+ case EXPANDED:
+ return Icon_Submenu;
+ case EACCESS:
+ return Icon_Questionmark;
+ }
+ return Icon_NOICON;
+}
+
+static int child_set_state_expand(struct child *this, struct folder *parent)
+{
+ int newstate = EACCESS;
+ if (this->folder == NULL)
+ this->folder = load_folder(parent, this->name);
+
+ if (this->folder != NULL)
+ {
+ if(this->folder->children_count == 0)
+ newstate = SELECTED;
+ else
+ newstate = EXPANDED;
+ }
+ this->state = newstate;
+ return newstate;
+}
+
+static int folder_action_callback(int action, struct gui_synclist *list)
+{
+ struct folder *root = (struct folder*)list->data;
+ struct folder *parent;
+ struct child *this = find_index(root, list->selected_item, &parent), *child;
+ int i;
+
+ if (action == ACTION_STD_OK)
+ {
+ switch (this->state)
+ {
+ case EXPANDED:
+ this->state = SELECTED;
+ break;
+ case SELECTED:
+ this->state = COLLAPSED;
+ break;
+ case COLLAPSED:
+ child_set_state_expand(this, parent);
+ break;
+ case EACCESS:
+ /* cannot open, do nothing */
+ return action;
+ }
+ action = ACTION_REDRAW;
+ }
+ else if (action == ACTION_STD_CONTEXT)
+ {
+ switch (this->state)
+ {
+ case EXPANDED:
+ for (i = 0; i < this->folder->children_count; i++)
+ {
+ child = &this->folder->children[i];
+ switch (child->state)
+ {
+ case SELECTED:
+ case EXPANDED:
+ child->state = COLLAPSED;
+ break;
+ case COLLAPSED:
+ child->state = SELECTED;
+ break;
+ case EACCESS:
+ break;
+ }
+ }
+ break;
+ case SELECTED:
+ case COLLAPSED:
+ if (child_set_state_expand(this, parent) != EACCESS)
+ {
+ for (i = 0; i < (this->folder->children_count); i++)
+ {
+ child = &this->folder->children[i];
+ child->state = SELECTED;
+ }
+ }
+ break;
+ case EACCESS:
+ /* cannot open, do nothing */
+ return action;
+ }
+ action = ACTION_REDRAW;
+ }
+ if (action == ACTION_REDRAW)
+ list->nb_items = count_items(root);
+ return action;
+}
+
+static struct child* find_from_filename(const char* filename, struct folder *root)
+{
+ if (!root)
+ return NULL;
+ const char *slash = rb->strchr(filename, '/');
+ struct child *this;
+
+ /* filenames beginning with a / are specially treated as the
+ * loop below can't handle them. they can only occur on the first,
+ * and not recursive, calls to this function.*/
+ if (filename[0] == '/') /* in the loop nothing starts with '/' */
+ {
+ logf("find_from_filename [%s]", filename);
+ /* filename begins with /. in this case root must be the
+ * top level folder */
+ this = &root->children[0];
+ if (filename[1] == '\0')
+ { /* filename == "/" */
+ return this;
+ }
+ else /* filename == "/XXX/YYY". cascade down */
+ goto cascade;
+ }
+
+ for (int i = 0; i < root->children_count; i++)
+ {
+ this = &root->children[i];
+ /* when slash == NULL n will be really large but \0 stops the compare */
+ if (rb->strncasecmp(this->name, filename, slash - filename) == 0)
+ {
+ if (slash == NULL)
+ { /* filename == XXX */
+ return this;
+ }
+ else
+ goto cascade;
+ }
+ }
+ return NULL;
+
+cascade:
+ /* filename == XXX/YYY. cascade down */
+ child_set_state_expand(this, root);
+ while (slash[0] == '/') slash++; /* eat slashes */
+ return find_from_filename(slash, this->folder);
+}
+
+static int select_paths(struct folder* root, const char* filenames)
+{
+ /* Takes a list of filenames in a ':' delimited string
+ splits filenames at the ':' character loads each into buffer
+ selects each file in the folder list
+
+ if last item or only item the rest of the string is copied to the buffer
+ *End the last item WITHOUT the ':' character /.rockbox/eqs:/.rockbox/wps\0*
+ */
+ char buf[MAX_PATH];
+ const int buflen = sizeof(buf);
+
+ const char *fnp = filenames;
+ const char *lastfnp = fnp;
+ const char *sstr;
+ off_t len;
+
+ while (fnp)
+ {
+ fnp = rb->strchr(fnp, ':');
+ if (fnp)
+ {
+ len = fnp - lastfnp;
+ fnp++;
+ }
+ else /* no ':' get the rest of the string */
+ len = rb->strlen(lastfnp);
+
+ sstr = lastfnp;
+ lastfnp = fnp;
+ if (len <= 0 || len > buflen)
+ continue;
+ rb->strlcpy(buf, sstr, len + 1);
+ struct child *item = find_from_filename(buf, root);
+ if (item)
+ item->state = SELECTED;
+ }
+
+ return 0;
+}
+
+static void save_folders_r(struct folder *root, char* dst, size_t maxlen, size_t buflen)
+{
+ size_t len;
+ struct folder *curfolder;
+ char* name;
+
+ for (int i = 0; i < root->children_count; i++)
+ {
+ struct child *this = &root->children[i];
+ if (this->state == SELECTED)
+ {
+ if (this->folder == NULL)
+ {
+ curfolder = root;
+ name = this->name;
+ logf("save_folders_r: this->name[%s]", name);
+ }
+ else
+ {
+ curfolder = this->folder->previous;
+ name = this->folder->name;
+ logf("save_folders_r: this->folder->name[%s]", name);
+ }
+
+ len = get_full_path(curfolder, buffer_front, buflen);
+
+ if (len + 2 >= buflen)
+ continue;
+
+ len += rb->snprintf(&buffer_front[len], buflen - len, "%s:", name);
+ logf("save_folders_r: [%s]", buffer_front);
+ if (dst != hashed.buf)
+ {
+ int dlen = rb->strlen(dst);
+ if (dlen + len >= maxlen)
+ continue;
+ rb->strlcpy(&dst[dlen], buffer_front, maxlen - dlen);
+ }
+ else
+ {
+ if (hashed.len + len >= maxlen)
+ {
+ hashed.maxlen_exceeded = 1;
+ continue;
+ }
+ get_hash(buffer_front, &hashed.val, len);
+ hashed.len += len;
+ }
+ }
+ else if (this->state == EXPANDED)
+ save_folders_r(this->folder, dst, maxlen, buflen);
+ }
+}
+
+static uint32_t save_folders(struct folder *root, char* dst, size_t maxlen)
+{
+ hashed.len = 0;
+ hashed.val = 0;
+ hashed.maxlen_exceeded = 0;
+ size_t len = buffer_end - buffer_front;
+ dst[0] = '\0';
+ save_folders_r(root, dst, maxlen, len);
+ len = rb->strlen(dst);
+ /* fix trailing ':' */
+ if (len > 1) dst[len-1] = '\0';
+ /*Notify - user will probably not see save dialog if nothing new got added*/
+ if (hashed.maxlen_exceeded > 0) rb->splash(HZ *2, ID2P(LANG_SHOWDIR_BUFFER_FULL));
+ return hashed.val;
+}
+
+bool folder_select(char * header_text, char* setting, int setting_len)
+{
+ struct folder *root;
+ struct simplelist_info info;
+ size_t buf_size;
+
+ buffer_front = rb->plugin_get_buffer(&buf_size);
+ buffer_end = buffer_front + buf_size;
+ logf("folder_select %d bytes free", (int)(buffer_end - buffer_front));
+ root = load_root();
+
+ logf("folders in: %s", setting);
+ /* Load previous selection(s) */
+ select_paths(root, setting);
+ /* get current hash to check for changes later */
+ uint32_t hash = save_folders(root, hashed.buf, setting_len);
+ rb->simplelist_info_init(&info, header_text,
+ count_items(root), root);
+ info.get_name = folder_get_name;
+ info.action_callback = folder_action_callback;
+ info.get_icon = folder_get_icon;
+ rb->simplelist_show_list(&info);
+ logf("folder_select %d bytes free", (int)(buffer_end - buffer_front));
+ /* done editing. check for changes */
+ if (hash != save_folders(root, hashed.buf, setting_len))
+ { /* prompt for saving changes and commit if yes */
+ if (rb->yesno_pop(ID2P(LANG_SAVE_CHANGES)))
+ {
+ save_folders(root, setting, setting_len);
+ rb->settings_save();
+ logf("folders out: %s", setting);
+ return true;
+ }
+ }
+ return false;
+}
+
+/* plugin entry point */
+enum plugin_status plugin_start(const void* parameter)
+{
+ (void) parameter;
+
+ if(parameter)
+ {
+
+ if (rb->strcmp(parameter, rb->str(LANG_AUTORESUME)) == 0)
+ {
+ if (folder_select(rb->str(LANG_AUTORESUME),
+ rb->global_settings->autoresume_paths,
+ MAX_PATHNAME+1))
+ {
+ return 1;
+ }
+ }
+ }
+ else if (folder_select(rb->str(LANG_SELECT_FOLDER),
+ rb->global_settings->tagcache_scan_paths,
+ sizeof(rb->global_settings->tagcache_scan_paths)))
+ {
+ return 1;
+ }
+
+ return PLUGIN_OK;
+}
diff --git a/apps/plugins/keybox.c b/apps/plugins/keybox.c
index 1689321abe..a074ffc598 100644
--- a/apps/plugins/keybox.c
+++ b/apps/plugins/keybox.c
@@ -75,7 +75,7 @@ static void encrypt_buffer(char *buf, size_t size, uint32_t *key);
static void decrypt_buffer(char *buf, size_t size, uint32_t *key);
/* the following two functions are the reference TEA implementation by
- David Wheeler and Roger Needham taken from
+ David Wheeler and Roger Needham taken from
http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm */
static void do_encrypt(uint32_t* v, uint32_t* k)
diff --git a/apps/plugins/lib/SOURCES b/apps/plugins/lib/SOURCES
index bdea07315e..1cd092f8df 100644
--- a/apps/plugins/lib/SOURCES
+++ b/apps/plugins/lib/SOURCES
@@ -1,6 +1,8 @@
sha1.c
gcc-support.c
pluginlib_actions.c
+action_helper.c
+button_helper.c
helper.c
icon_helper.c
arg_helper.c
diff --git a/apps/plugins/lib/action_helper.c b/apps/plugins/lib/action_helper.c
new file mode 100644
index 0000000000..906051c1ea
--- /dev/null
+++ b/apps/plugins/lib/action_helper.c
@@ -0,0 +1 @@
+/*DUMMY_FILE_DONT_CHANGEME*/
diff --git a/apps/plugins/lib/action_helper.h b/apps/plugins/lib/action_helper.h
new file mode 100644
index 0000000000..58d9c6c303
--- /dev/null
+++ b/apps/plugins/lib/action_helper.h
@@ -0,0 +1,34 @@
+/***************************************************************************
+* __________ __ ___.
+* Open \______ \ ____ ____ | | _\_ |__ _______ ___
+* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+* \/ \/ \/ \/ \/
+* $Id$
+*
+* Copyright (C) 2021 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.
+*
+****************************************************************************/
+/* action_helper provides a way to turn numeric action/context into strings
+* the file action_helper.c is generated at compile time
+* ACTION_ and CONTEXT_ are stripped from the strings and replaced when
+* action_name and context_name are called,
+* NOTE: both share the same static buffer sized as the largest string possible
+*/
+#ifndef _ACTION_HELPER_H_
+#define _ACTION_HELPER_H_
+
+char* action_name(int action);
+char* context_name(int context);
+
+#endif /* _ACTION_HELPER_H_ */
diff --git a/apps/plugins/lib/action_helper.pl b/apps/plugins/lib/action_helper.pl
new file mode 100755
index 0000000000..1dfdcfd070
--- /dev/null
+++ b/apps/plugins/lib/action_helper.pl
@@ -0,0 +1,209 @@
+#!/usr/bin/env perl
+############################################################################
+# __________ __ ___.
+# Open \______ \ ____ ____ | | _\_ |__ _______ ___
+# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+# \/ \/ \/ \/ \/
+# $action_helper$
+#
+# Copyright (C) 2021 William Wilgus
+#
+# All files in this archive are subject to the GNU General Public License.
+# See the file COPYING in the source tree root for full license agreement.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+############################################################################
+#expects -E source input on STDIN
+use strict;
+use warnings;
+
+my @actions = ();
+my @contexts = ();
+my @action_offset = ();
+my @context_offset = ();
+my $action_ct = 0;
+my $context_ct = 0;
+my $len_max_action = 0;
+my $len_max_context = 0;
+my $len_min_action = -1;
+my $len_min_context = -1;
+while(my $line = <STDIN>)
+{
+ chomp($line);
+ if($line =~ /^\s*(ACTION_[^\s]+)(\s*=.*)?,\s*$/)
+ {
+ $actions[$action_ct] = $1;
+ $action_ct++;
+ }
+ elsif($line =~ /^\s*(LAST_ACTION_PLACEHOLDER)(\s*=.*)?,\s*$/)
+ { #special case don't save actual name
+ $actions[$action_ct] = "";
+ $action_ct++;
+ }
+ elsif($line =~ /^\s*(PLA_[^\s]+)(\s*=.*)?,\s*$/)
+ {
+ $actions[$action_ct] = $1;
+ $action_ct++;
+ }
+ elsif($line =~ /^\s*(CONTEXT_[^\s]+)(\s*=.*)?,\s*$/)
+ {
+ $contexts[$context_ct] = $1;
+ $context_ct++;
+ }
+}
+
+print <<EOF
+/* Don't change this file! */
+/* It is automatically generated of action.h */
+#include "plugin.h"
+#include "action_helper.h"
+EOF
+;
+#dump actions
+my $offset = 0;
+print "static const char action_names[]= \n";
+for(my $i = 0; $i < $action_ct; $i++){
+ my $act = $actions[$i];
+ $act =~ s/ACTION_USB_HID_/%s/ig; # strip the common part
+ $act =~ s/ACTION_/%s/ig; # strip the common part
+ my $actlen = length($act);
+ if ($actlen < $len_min_action or $len_min_action == -1){
+ $len_min_action = $actlen;
+ }
+ if ($actions[$i] ne $act){
+ printf "/*%s*/\"%s\\0\"\n", substr($actions[$i], 0, -($actlen - 2)), $act;
+ } else {
+ print "\"$act\\0\" \n";
+ }
+ my $slen = length($actions[$i]) + 1; #NULL terminator
+ if ($slen > $len_max_action) { $len_max_action = $slen; }
+ push(@action_offset, {'name' => $actions[$i], 'offset' => $offset});
+ $offset += length($act) + 1; # NULL terminator
+}
+printf "\"\";/* %d + \\0 */\n\n", $offset;
+@actions = ();
+
+#dump contexts
+$offset = 0;
+print "static const char context_names[]= \n";
+for(my $i = 0; $i < $context_ct; $i++){
+ my $ctx = $contexts[$i];
+ $ctx =~ s/CONTEXT_/%s/ig; # strip the common part
+ my $ctxlen = length($ctx);
+
+ if ($ctxlen < 5){
+ $ctx = $contexts[$i];
+ $ctxlen = length($ctx);
+ }
+
+ if ($ctxlen < $len_min_context or $len_min_context == -1){
+ $len_min_context = $ctxlen;
+ }
+ if ($contexts[$i] ne $ctx){
+ printf "/*%s*/\"%s\\0\"\n", substr($contexts[$i], 0, -($ctxlen - 2)), $ctx;
+ } else {
+ print "\"$ctx\\0\" \n";
+ }
+ my $slen = length($contexts[$i]) + 1; # NULL terminator
+ if ($slen > $len_max_context) { $len_max_context = $slen; }
+ push(@context_offset, {'name' => $contexts[$i], 'offset' => $offset});
+ $offset += length($ctx) + 1; # NULL terminator
+}
+printf "\"\";/* %d + \\0 */\n\n", $offset;
+@contexts = ();
+
+printf "#define ACTION_CT %d\n", $action_ct;
+print "static const uint16_t action_offsets[ACTION_CT] = {\n";
+foreach my $define (@action_offset)
+{
+ printf("%d, /*%s*/\n", @$define{'offset'}, @$define{'name'});
+}
+print "};\n\n";
+@action_offset = ();
+
+printf "#define CONTEXT_CT %d\n", $context_ct;
+print "#if 0 /* context_names is small enough to walk the string instead */\n";
+print "static const uint16_t context_offsets[CONTEXT_CT] = {\n";
+foreach my $define (@context_offset)
+{
+ printf("%d, /*%s*/\n", @$define{'offset'}, @$define{'name'});
+}
+print "};\n#endif\n\n";
+@context_offset = ();
+
+printf "#define ACTIONBUFSZ %d\n", $len_max_action;
+printf "#define CONTEXTBUFSZ %d\n\n", $len_max_context;
+
+if ($len_max_action > $len_max_context)
+{
+ print "static char name_buf[ACTIONBUFSZ];\n";
+}
+else
+{
+ print "static char name_buf[CONTEXTBUFSZ];\n";
+}
+print <<EOF
+
+char* action_name(int action)
+{
+ if (action >= 0 && action < ACTION_CT)
+ {
+ uint16_t offset = action_offsets[action];
+ const char *act = &action_names[offset];
+ if (action < ACTION_USB_HID_FIRST)
+ rb->snprintf(name_buf, ACTIONBUFSZ, act, "ACTION_");
+ else
+ rb->snprintf(name_buf, ACTIONBUFSZ, act, "ACTION_USB_HID_");
+ }
+ else
+ rb->snprintf(name_buf, ACTIONBUFSZ, "ACTION_UNKNOWN");
+ return name_buf;
+}
+
+/* walk string increment offset for each NULL if desired offset found, return */
+static const char *context_getoffset(int offset)
+{
+ const char *names = context_names;
+ const size_t len = sizeof(context_names) - 1;
+ int current = 0;
+ if (offset > 0)
+ {
+ const char *pos = names;
+ const char *end = names + len;
+ while (pos < end)
+ {
+ if (*pos++ == '\\0')
+ {
+ current++;
+ if (offset == current)
+ return pos;
+ pos += $len_min_context; /* each string is at least this long */
+ }
+ }
+ }
+ return names;
+}
+
+char* context_name(int context)
+{
+ const char *ctx;
+ if (context >= 0 && context < CONTEXT_CT)
+ {
+#if 0
+ uint16_t offset = context_offsets[context];
+ ctx = &context_names[offset];
+#else
+ ctx = context_getoffset(context);
+#endif
+ }
+ else
+ ctx = "%sUNKNOWN";
+ rb->snprintf(name_buf, CONTEXTBUFSZ, ctx, "CONTEXT_");
+ return name_buf;
+}
+EOF
+;
diff --git a/apps/plugins/lib/arg_helper.c b/apps/plugins/lib/arg_helper.c
index d402300900..3ea5ba714d 100644
--- a/apps/plugins/lib/arg_helper.c
+++ b/apps/plugins/lib/arg_helper.c
@@ -28,7 +28,9 @@
#define SWCHAR '-'
#define DECSEPCHAR '.'
-
+#ifdef PLUGIN
+ #define strchr rb->strchr
+#endif
int string_parse(const char **parameter, char* buf, size_t buf_sz)
{
/* fills buf with a string upto buf_sz, null terminates the buffer
diff --git a/apps/plugins/lib/arg_helper.h b/apps/plugins/lib/arg_helper.h
index c7b14f7f7a..2cf94ba1dd 100644
--- a/apps/plugins/lib/arg_helper.h
+++ b/apps/plugins/lib/arg_helper.h
@@ -26,7 +26,7 @@
#define ARGPARSE_MAX_FRAC_DIGITS 9 /* Uses 30 bits max (0.999999999) */
#define ARGP_EXP(a, b) (a ##E## b)
-#define ARGP_FRAC_DEC_MULTIPLIER(n) AP_EXP(1,n) /*1x10^n*/
+#define ARGP_FRAC_DEC_MULTIPLIER(n) ARGP_EXP(1,n) /*1x10^n*/
#define ARGPARSE_FRAC_DEC_MULTIPLIER \
(long)ARGP_FRAC_DEC_MULTIPLIER(ARGPARSE_MAX_FRAC_DIGITS)
diff --git a/apps/plugins/lib/button_helper.c b/apps/plugins/lib/button_helper.c
new file mode 100644
index 0000000000..906051c1ea
--- /dev/null
+++ b/apps/plugins/lib/button_helper.c
@@ -0,0 +1 @@
+/*DUMMY_FILE_DONT_CHANGEME*/
diff --git a/apps/plugins/lib/button_helper.h b/apps/plugins/lib/button_helper.h
new file mode 100644
index 0000000000..1197b172b0
--- /dev/null
+++ b/apps/plugins/lib/button_helper.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+* __________ __ ___.
+* Open \______ \ ____ ____ | | _\_ |__ _______ ___
+* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+* \/ \/ \/ \/ \/
+* $Id$
+*
+* Copyright (C) 2021 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.
+*
+****************************************************************************/
+#ifndef _BUTTON_HELPER_H_
+#define _BUTTON_HELPER_H_
+struct available_button
+{
+ const char* name;
+ unsigned long value;
+};
+
+/* *available_buttons is holding a pointer to the first element of an array
+ * of struct available_button it is set up in such a way due to the file being
+ * generated at compile time you can still call it as such though
+* eg available_buttons[0] or available_buttons[available_button_count] (NULL SENTINEL, 0)*/
+
+extern const struct available_button * const available_buttons;
+extern const int available_button_count;
+int get_button_names(char *buf, size_t bufsz, unsigned long button);
+#endif /* _BUTTON_HELPER_H_ */
diff --git a/apps/plugins/lib/button_helper.pl b/apps/plugins/lib/button_helper.pl
new file mode 100755
index 0000000000..45c3fd9073
--- /dev/null
+++ b/apps/plugins/lib/button_helper.pl
@@ -0,0 +1,98 @@
+#!/usr/bin/env perl
+############################################################################
+# __________ __ ___.
+# Open \______ \ ____ ____ | | _\_ |__ _______ ___
+# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+# \/ \/ \/ \/ \/
+# $Id$
+#
+# Copyright (C) 2009 by Maurus Cuelenaere
+# Copyright (C) 2021 by William Wilgus
+#
+# All files in this archive are subject to the GNU General Public License.
+# See the file COPYING in the source tree root for full license agreement.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+############################################################################
+#expects -dM -E source input on STDIN
+use strict;
+use warnings;
+my $svnrev = '$Revision$';
+my @buttons = ();
+my $count = 1; #null sentinel
+my $val;
+my $def;
+while(my $line = <STDIN>)
+{
+ chomp($line);
+ if($line =~ /^#define (BUTTON_[^\s]+) (.+)$/)
+ {
+ $def = "{\"$1\", $2},\n";
+ $val = $2;
+ if($val =~ /^0/)
+ {
+ $val = oct($val)
+ }
+ else
+ {
+ $val = 0xFFFFFFFF; #only used for sorting
+ }
+ push(@buttons, {'name' => $1, 'value' => $val, 'def' => $def});
+ $count = $count + 1;
+ }
+}
+my @sorted = sort { @$a{'value'} <=> @$b{'value'} } @buttons;
+print <<EOF
+/* Don't change this file! */
+/* It is automatically generated of button.h */
+#include "plugin.h"
+#include "button.h"
+#include "button_helper.h"
+
+static const struct available_button buttons[$count] = {
+EOF
+;
+$count--; # don't count the sentinel
+foreach my $button (@sorted)
+{
+ printf " %s", @$button{'def'};
+}
+
+print <<EOF
+ {"\\0", 0} /* sentinel */
+};
+const int available_button_count = $count;
+const struct available_button * const available_buttons = buttons;
+
+int get_button_names(char *buf, size_t bufsz, unsigned long button)
+{
+ int len = 0;
+ buf[0] = '\\0';
+ const struct available_button *btn = buttons;
+ while(btn->name[0] != '\\0')
+ {
+ if(btn->value == 0)
+ {
+ if (button == 0)
+ {
+ buf[0] = '\\0';
+ len = rb->strlcat(buf, btn->name, bufsz);
+ return len;
+ }
+ }
+ else if ((button & btn->value) == btn->value)
+ {
+ if (len > 0)
+ rb->strlcat(buf, " | ", bufsz);
+ len = rb->strlcat(buf, btn->name, bufsz);
+ }
+ btn++;
+ }
+ return len;
+}
+EOF
+;
diff --git a/apps/plugins/lrcplayer.c b/apps/plugins/lrcplayer.c
index 225f0c8689..f42b96b5b3 100644
--- a/apps/plugins/lrcplayer.c
+++ b/apps/plugins/lrcplayer.c
@@ -428,8 +428,8 @@ static struct lrc_brpos *calc_brpos(struct lrc_line *lrc_line, int i)
int nword;
int word_count, word_width;
const unsigned char *str;
- }
- sp,
+ }
+ sp,
cr;
lrc_buffer_used = (lrc_buffer_used+3)&~3; /* 4 bytes aligned */
diff --git a/apps/plugins/lua/rbdefines_helper.pl b/apps/plugins/lua/rbdefines_helper.pl
index e788855e87..5fb0946a6a 100755
--- a/apps/plugins/lua/rbdefines_helper.pl
+++ b/apps/plugins/lua/rbdefines_helper.pl
@@ -50,6 +50,7 @@ if ($def_type eq "rb_defines") {
'^SYS_(TIMEOUT|POWEROFF|BATTERY_UPDATE)$',
'^SYS_USB_(DIS|)CONNECTED$',
'^HOME_DIR$',
+ '^PLUGIN(_OK|_USB_CONNECTED|_POWEROFF|_GOTO_WPS|_GOTO_PLUGIN)$',
'^PLUGIN_DIR$',
'^PLUGIN(_APPS_|_GAMES_|_)DATA_DIR$',
'^ROCKBOX_DIR$',
diff --git a/apps/plugins/lua/rocklua.c b/apps/plugins/lua/rocklua.c
index 3909f3008f..3cf0fce945 100644
--- a/apps/plugins/lua/rocklua.c
+++ b/apps/plugins/lua/rocklua.c
@@ -175,9 +175,10 @@ static int loadfile_newstate(lua_State **L, const char *filename)
static void lua_atexit(void)
{
char *filename;
-
+ int err_n;
if(Ls && lua_gettop(Ls) > 1)
{
+ err_n = lua_tointeger(Ls, -1); /* os.exit? */
if (Ls == lua_touserdata(Ls, -1)) /* signal from restart_lua */
{
filename = (char *) malloc((MAX_PATH * 2) + 1);
@@ -195,7 +196,12 @@ static void lua_atexit(void)
free(filename);
plugin_start(NULL);
}
- else if (lua_tointeger(Ls, -1) != 0) /* os.exit */
+ else if (err_n >= PLUGIN_USB_CONNECTED) /* INTERNAL PLUGIN RETVAL */
+ {
+ lua_close(Ls);
+ _exit(err_n); /* don't call exit handler */
+ }
+ else if (err_n != 0)
{
ERR_RUN:
lu_status = LUA_ERRRUN;
@@ -205,7 +211,7 @@ ERR_RUN:
else
lua_close(Ls);
}
- _exit(0); /* don't call exit handler */
+ _exit(PLUGIN_OK); /* don't call exit handler */
}
/* split filename at argchar
diff --git a/apps/plugins/lua_scripts/dbgettags.lua b/apps/plugins/lua_scripts/dbgettags.lua
index ec6e29a330..8e9f26393d 100644
--- a/apps/plugins/lua_scripts/dbgettags.lua
+++ b/apps/plugins/lua_scripts/dbgettags.lua
@@ -28,13 +28,14 @@ local CANCEL_BUTTON = rb.actions.PLA_CANCEL
local sINVALIDDATABASE = "Invalid Database"
local sERROROPENING = "Error opening"
--- tag cache header
-local sTCVERSION = string.char(0x0F)
-local sTCHEADER = string.reverse("TCH" .. sTCVERSION)
-local DATASZ = 4 -- int32_t
-local TCHSIZE = 3 * DATASZ -- 3 x int32_t
-
-local function bytesLE_n(str)
+-- tag cache header
+sTCVERSION = string.char(0x10)
+sTCHEADER = string.reverse("TCH" .. sTCVERSION)
+DATASZ = 4 -- int32_t
+TCHSIZE = 3 * DATASZ -- 3 x int32_t
+
+-- Converts array of bytes to proper endian
+function bytesLE_n(str)
str = str or ""
local tbyte={str:byte(1, -1)}
local bpos = 1
diff --git a/apps/plugins/lua_scripts/random_playlist.lua b/apps/plugins/lua_scripts/random_playlist.lua
new file mode 100644
index 0000000000..6e4dbe25e2
--- /dev/null
+++ b/apps/plugins/lua_scripts/random_playlist.lua
@@ -0,0 +1,464 @@
+--[[ Lua RB Random Playlist -- random_playlist.lua V 1.0
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2021 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.
+ *
+ ****************************************************************************/
+]]
+require ("actions")
+require("dbgettags")
+get_tags = nil -- unneeded
+
+-- User defaults
+local playlistpath = "/Playlists"
+local max_tracks = 500; -- size of playlist to create
+local min_repeat = 500; -- this many songs before a repeat
+local play_on_success = true;
+
+-- Random integer function
+local random = math.random; -- ref random(min, max)
+math.randomseed(rb.current_tick()); -- some kind of randomness
+
+-- Button definitions
+local CANCEL_BUTTON = rb.actions.PLA_CANCEL
+local OK_BUTTON = rb.actions.PLA_SELECT
+local ADD_BUTTON = rb.actions.PLA_UP
+local ADD_BUTTON_RPT = rb.actions.PLA_UP_REPEAT or ADD_BUTTON
+local SUB_BUTTON = rb.actions.PLA_DOWN
+local SUB_BUTTON_RPT = rb.actions.PLA_DOWN_REPEAT or SUB_BUTTON
+-- remove action and context tables to free some ram
+rb.actions = nil
+rb.contexts = nil
+-- Program strings
+local sINITDATABASE = "Initialize Database"
+local sHEADERTEXT = "Random Playlist"
+local sPLAYLISTERROR = "Playlist Error!"
+local sREMOVEPLAYLIST = "Removing Dynamic Playlist"
+local sSEARCHINGFILES = "Searching for Files.."
+local sERROROPENFMT = "Error Opening %s"
+local sINVALIDDBFMT = "Invalid Database %s"
+local sPROGRESSHDRFMT = "%d \\ %d Tracks"
+local sGOODBYE = "Goodbye"
+
+-- Gets size of text
+local function text_extent(msg, font)
+ font = font or rb.FONT_UI
+ return rb.font_getstringsize(msg, font)
+end
+
+local function _setup_random_playlist(tag_entries, play, min_repeat, trackcount)
+ -- Setup string tables
+ local tPLAYTEXT = {"Play? [ %s ] (up/dn)", "true = play tracks on success"}
+ local tREPEATTEXT = {"Repeat hist? [ %d ] (up/dn)","higher = less repeated songs"}
+ local tPLSIZETEXT = {"Find [ %d ] tracks? (up/dn)",
+ "Warning overwrites dynamic playlist",
+ "Press back to cancel"};
+ -- how many lines can we fit on the screen?
+ local res, w, h = text_extent("I")
+ h = h + 5 -- increase spacing in the setup menu
+ local max_w = rb.LCD_WIDTH / w
+ local max_h = rb.LCD_HEIGHT - h
+ local y = 0
+
+ -- User Setup Menu
+ local action, ask, increment
+ local t_desc = {scroll = true} -- scroll the setup items
+
+ -- Clears screen and adds title and icon, called first..
+ function show_setup_header()
+ local desc = {icon = 2, show_icons = true, scroll = true} -- 2 == Icon_Playlist
+ rb.lcd_clear_display()
+ rb.lcd_put_line(1, 0, sHEADERTEXT, desc)
+ end
+
+ -- Display up to 3 items and waits for user action -- returns action
+ function ask_user_action(desc, ln1, ln2, ln3)
+ if ln1 then rb.lcd_put_line(1, h, ln1, desc) end
+ if ln2 then rb.lcd_put_line(1, h + h, ln2, desc) end
+ if ln3 then rb.lcd_put_line(1, h + h + h, ln3, desc) end
+ rb.lcd_hline(1,rb.LCD_WIDTH - 1, h - 5);
+ rb.lcd_update()
+
+ local act = rb.get_plugin_action(-1); -- Blocking wait for action
+ -- handle magnitude of the increment here so consumer fn doesn't need to
+ if act == ADD_BUTTON_RPT and act ~= ADD_BUTTON then
+ increment = increment + 1
+ if increment > 1000 then increment = 1000 end
+ act = ADD_BUTTON
+ elseif act == SUB_BUTTON_RPT and act ~= SUB_BUTTON then
+ increment = increment + 1
+ if increment > 1000 then increment = 1000 end
+ act = SUB_BUTTON
+ else
+ increment = 1;
+ end
+
+ return act
+ end
+
+ -- Play the playlist on successful completion true/false?
+ function setup_get_play()
+ action = ask_user_action(tdesc,
+ string.format(tPLAYTEXT[1], tostring(play)),
+ tPLAYTEXT[2]);
+ if action == ADD_BUTTON then
+ play = true
+ elseif action == SUB_BUTTON then
+ play = false
+ end
+ end
+
+ -- Repeat song buffer list of previously added tracks 0-??
+ function setup_get_repeat()
+ if min_repeat >= trackcount then min_repeat = trackcount - 1 end
+ if min_repeat >= tag_entries then min_repeat = tag_entries - 1 end
+ action = ask_user_action(t_desc,
+ string.format(tREPEATTEXT[1],min_repeat),
+ tREPEATTEXT[2]);
+ if action == ADD_BUTTON then
+ min_repeat = min_repeat + increment
+ elseif action == SUB_BUTTON then -- MORE REPEATS LESS RAM USED
+ if min_repeat < increment then increment = 1 end
+ min_repeat = min_repeat - increment
+ if min_repeat < 0 then min_repeat = 0 end
+ elseif action == OK_BUTTON then
+ ask = setup_get_play;
+ setup_get_repeat = nil
+ action = 0
+ end
+ end
+
+ -- How many tracks to find
+ function setup_get_playlist_size()
+ action = ask_user_action(t_desc,
+ string.format(tPLSIZETEXT[1], trackcount),
+ tPLSIZETEXT[2],
+ tPLSIZETEXT[3]);
+ if action == ADD_BUTTON then
+ trackcount = trackcount + increment
+ elseif action == SUB_BUTTON then
+ if trackcount < increment then increment = 1 end
+ trackcount = trackcount - increment
+ if trackcount < 1 then trackcount = 1 end
+ elseif action == OK_BUTTON then
+ ask = setup_get_repeat;
+ setup_get_playlist_size = nil
+ action = 0
+ end
+ end
+ ask = setup_get_playlist_size; -- \!FIRSTRUN!/
+
+ repeat -- SETUP MENU LOOP
+ show_setup_header()
+ ask()
+ rb.lcd_scroll_stop() -- I'm still wary of not doing this..
+ collectgarbage("collect")
+ if action == CANCEL_BUTTON then rb.lcd_scroll_stop(); return nil end
+ until (action == OK_BUTTON)
+
+ return play, min_repeat, trackcount;
+end
+
+--[[ Given the filenameDB file [database]
+ creates a random dynamic playlist with a default savename of [playlist]
+ containing [trackcount] tracks, played on completion if [play] is true]]
+function create_random_playlist(database, playlist, trackcount, play)
+ if not database or not playlist or not trackcount then return end
+ if not play then play = false end
+
+ local file = io.open('/' .. database or "", "r") --read
+ if not file then rb.splash(100, string.format(sERROROPENFMT, database)) return end
+
+ local fsz = file:seek("end")
+ local fbegin
+ local posln = 0
+ local tag_len = TCHSIZE
+
+ local anchor_index
+ local ANCHOR_INTV
+ local track_index = setmetatable({},{__mode = "v"}) --[[ weak table values
+ this allows them to be garbage collected as space is needed / rebuilt as needed ]]
+
+ -- Read character function sets posln as file position
+ function readchrs(count)
+ if posln >= fsz then return nil end
+ file:seek("set", posln)
+ posln = posln + count
+ return file:read(count)
+ end
+
+ -- Check the header and get size + #entries
+ local tagcache_header = readchrs(DATASZ) or ""
+ local tagcache_sz = readchrs(DATASZ) or ""
+ local tagcache_entries = readchrs(DATASZ) or ""
+
+ if tagcache_header ~= sTCHEADER or
+ bytesLE_n(tagcache_sz) ~= (fsz - TCHSIZE) then
+ rb.splash(100, string.format(sINVALIDDBFMT, database))
+ return
+ end
+
+ local tag_entries = bytesLE_n(tagcache_entries)
+
+ play, min_repeat, trackcount = _setup_random_playlist(
+ tag_entries, play, min_repeat, trackcount);
+ _setup_random_playlist = nil
+ collectgarbage("collect")
+
+ -- how many lines can we fit on the screen?
+ local res, w, h = text_extent("I")
+ local max_w = rb.LCD_WIDTH / w
+ local max_h = rb.LCD_HEIGHT - h
+ local y = 0
+ rb.lcd_clear_display()
+
+ function get_tracks_random()
+ local tries, idxp
+
+ local tracks = 0
+ local str = ""
+ local t_lru = {}
+ local lru_widx = 1
+ local lru_max = min_repeat
+ if lru_max >= tag_entries then lru_max = tag_entries / 2 + 1 end
+
+ function do_progress_header()
+ rb.lcd_put_line(1, 0, string.format(sPROGRESSHDRFMT,tracks, trackcount))
+ rb.lcd_update()
+ --rb.sleep(300)
+ end
+
+ function show_progress()
+ local sdisp = str:match("([^/]+)$") or "?" --just the track name
+ rb.lcd_put_line(1, y, sdisp:sub(1, max_w));-- limit string length
+ y = y + h
+ if y >= max_h then
+ do_progress_header()
+ rb.lcd_clear_display()
+ y = h
+ end
+ end
+
+ -- check for repeated tracks
+ function check_lru(val)
+ if lru_max <= 0 or val == nil then return 0 end --user wants all repeats
+ local rv
+ local i = 1
+ repeat
+ rv = t_lru[i]
+ if rv == nil then
+ break;
+ elseif rv == val then
+ return i
+ end
+ i = i + 1
+ until (i == lru_max)
+ return 0
+ end
+
+ -- add a track to the repeat list (overwrites oldest if full)
+ function push_lru(val)
+ t_lru[lru_widx] = val
+ lru_widx = lru_widx + 1
+ if lru_widx > lru_max then lru_widx = 1 end
+ end
+
+ function get_index()
+ if ANCHOR_INTV > 1 then
+ get_index =
+ function(plidx)
+ local p = track_index[plidx]
+ if p == nil then
+ parse_database_offsets(plidx)
+ end
+ return track_index[plidx][1]
+ end
+ else -- all tracks are indexed
+ get_index =
+ function(plidx)
+ return track_index[plidx]
+ end
+ end
+ end
+
+ get_index() --init get_index fn
+ -- Playlist insert loop
+ while true do
+ str = nil
+ tries = 0
+ repeat
+ idxp = random(1, tag_entries)
+ tries = tries + 1 -- prevent endless loops
+ until check_lru(idxp) == 0 or tries > fsz -- check for recent repeats
+
+ posln = get_index(idxp)
+
+ tag_len = bytesLE_n(readchrs(DATASZ))
+ posln = posln + DATASZ -- idx = bytesLE_n(readchrs(DATASZ))
+ str = readchrs(tag_len) or "\0" -- Read the database string
+ str = str:match("^(%Z+)%z$") -- \0 terminated string
+
+ -- Insert track into playlist
+ if str ~= nil then
+ tracks = tracks + 1
+ show_progress()
+ push_lru(idxp) -- add to repeat list
+ if rb.playlist("insert_track", str) < 0 then
+ rb.splash(rb.HZ, sPLAYLISTERROR)
+ break; -- ERROR, PLAYLIST FULL?
+ end
+
+ end
+
+ if tracks >= trackcount then
+ do_progress_header()
+ break
+ end
+
+ -- check for cancel non-blocking
+ if rb.get_plugin_action(0) == CANCEL_BUTTON then
+ break
+ end
+ end
+ end -- get_files
+
+ function build_anchor_index()
+ -- index every n files
+ ANCHOR_INTV = 1 -- for small db we can put all the entries in ram
+ local ent = tag_entries / 1000 -- more than 10,000 will be incrementally loaded
+ while ent >= 10 do -- need to reduce the size of the anchor index?
+ ent = ent / 10
+ ANCHOR_INTV = ANCHOR_INTV * 10
+ end -- should be power of 10 (10, 100, 1000..)
+ --grab an index for every ANCHOR_INTV entries
+ local aidx={}
+ local acount = 0
+ local next_idx = 1
+ local index = 1
+ local tlen
+ if ANCHOR_INTV == 1 then acount = 1 end
+ while index <= tag_entries and posln < fsz do
+ if next_idx == index then
+ acount = acount + 1
+ next_idx = acount * ANCHOR_INTV
+ aidx[index] = posln
+ else -- fill the weak table, we already did the work afterall
+ track_index[index] = {posln} -- put vals inside table to make them collectable
+ end
+ index = index + 1
+ tlen = bytesLE_n(readchrs(DATASZ))
+ posln = posln + tlen + DATASZ
+ end
+ return aidx
+ end
+
+ function parse_database_offsets(plidx)
+ local tlen
+ -- round to nearest anchor entry that is less than plidx
+ local aidx = (plidx / ANCHOR_INTV) * ANCHOR_INTV
+ local cidx = aidx
+ track_index[cidx] = {anchor_index[aidx] or fbegin};
+ -- maybe we can use previous work to get closer to the desired offset
+ while track_index[cidx] ~= nil and cidx <= plidx do
+ cidx = cidx + 1 --keep seeking till we find an empty entry
+ end
+ posln = track_index[cidx - 1][1]
+ while cidx <= plidx do --[[ walk the remaining entries from the last known
+ & save the entries on the way to our desired entry ]]
+ tlen = bytesLE_n(readchrs(DATASZ))
+ posln = posln + tlen + DATASZ
+ track_index[cidx] = {posln} -- put vals inside table to make them collectable
+ if posln >= fsz then posln = fbegin end
+ cidx = cidx + 1
+ end
+ end
+
+ if trackcount ~= nil then
+ rb.splash(10, sSEARCHINGFILES)
+ fbegin = posln --Mark the beginning for later loops
+ tag_len = 0
+ anchor_index = build_anchor_index() -- index track offsets
+ if ANCHOR_INTV == 1 then
+ -- all track indexes are in ram
+ track_index = anchor_index
+ anchor_index = nil
+ end
+
+ rb.splash(10, sREMOVEPLAYLIST)
+ rb.audio("stop")
+ os.remove( playlistpath .. "/" .. playlist)
+ rb.playlist("remove_all_tracks")
+ rb.playlist("create", playlistpath .. "/", playlist)
+--[[ --profiling
+ local starttime = rb.current_tick();
+ get_tracks_random()
+ local endtime = rb.current_tick();
+ rb.splash(1000, (endtime - starttime) .. " ticks");
+ end
+ if (false) then
+--]]
+ get_tracks_random()
+ end
+
+ file:close()
+ collectgarbage("collect")
+ if trackcount and rb.playlist("amount") >= trackcount and play == true then
+ rb.playlist("start", 0, 0, 0)
+ end
+
+end -- create_playlist
+
+local function main()
+ if not rb.file_exists(rb.ROCKBOX_DIR .. "/database_4.tcd") then
+ rb.splash(rb.HZ, sINITDATABASE)
+ os.exit(1);
+ end
+ if rb.cpu_boost then rb.cpu_boost(true) end
+ rb.backlight_force_on()
+ if not rb.dir_exists(playlistpath) then
+ luadir.mkdir(playlistpath)
+ end
+ rb.lcd_clear_display()
+ rb.lcd_update()
+ collectgarbage("collect")
+ create_random_playlist(rb.ROCKBOX_DIR .. "/database_4.tcd",
+ "random_playback.m3u8", max_tracks, play_on_success);
+ rb.splash(rb.HZ * 2, sGOODBYE)
+ -- Restore user backlight settings
+ rb.backlight_use_settings()
+ if rb.cpu_boost then rb.cpu_boost(false) end
+
+--[[
+local used, allocd, free = rb.mem_stats()
+local lu = collectgarbage("count")
+local fmt = function(t, v) return string.format("%s: %d Kb\n", t, v /1024) end
+
+-- this is how lua recommends to concat strings rather than ..
+local s_t = {}
+s_t[1] = "rockbox:\n"
+s_t[2] = fmt("Used ", used)
+s_t[3] = fmt("Allocd ", allocd)
+s_t[4] = fmt("Free ", free)
+s_t[5] = "\nlua:\n"
+s_t[6] = fmt("Used", lu * 1024)
+s_t[7] = "\n\nNote that the rockbox used count is a high watermark"
+rb.splash_scroller(10 * rb.HZ, table.concat(s_t)) --]]
+
+end --MAIN
+
+main() -- BILGUS
diff --git a/apps/plugins/lua_scripts/return2WPS.lua b/apps/plugins/lua_scripts/return2WPS.lua
new file mode 100644
index 0000000000..9202237691
--- /dev/null
+++ b/apps/plugins/lua_scripts/return2WPS.lua
@@ -0,0 +1,19 @@
+--[[
+ __________ __ ___.
+ Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ \/ \/ \/ \/ \/
+ $Id$
+ Example Lua Return to WPS on exit
+ Copyright (C) 2021 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.
+]]--
+
+os.exit(rb.PLUGIN_GOTO_WPS)
diff --git a/apps/plugins/main_menu_config.c b/apps/plugins/main_menu_config.c
index f66165e63d..9f651094b1 100644
--- a/apps/plugins/main_menu_config.c
+++ b/apps/plugins/main_menu_config.c
@@ -171,7 +171,7 @@ enum plugin_status plugin_start(const void* parameter)
struct gui_synclist list;
bool done = false;
int action, cur_sel;
-
+
menu_table = rb->root_menu_get_options(&menu_item_count);
load_from_cfg();
diff --git a/apps/plugins/open_plugins.c b/apps/plugins/open_plugins.c
index d479bbd31c..3a0c34d8d6 100644
--- a/apps/plugins/open_plugins.c
+++ b/apps/plugins/open_plugins.c
@@ -49,7 +49,8 @@
static int fd_dat;
static struct gui_synclist lists;
struct open_plugin_entry_t op_entry;
-const off_t op_entry_sz = sizeof(struct open_plugin_entry_t);
+static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM;
+static const off_t op_entry_sz = sizeof(struct open_plugin_entry_t);
/* we only need the names for the first menu so don't bother reading paths yet */
const off_t op_name_sz = OPEN_PLUGIN_NAMESZ + (op_entry.name - (char*)&op_entry);
@@ -101,6 +102,15 @@ static bool op_entry_read_name(int fd, int selected_item)
return op_entry_read(fd, selected_item, op_name_sz);
}
+static int op_entry_checksum(void)
+{
+ if (op_entry.checksum != open_plugin_csum)
+ {
+ return 0;
+ }
+ return 1;
+}
+
static int op_entry_read_opx(const char *path)
{
int ret = -1;
@@ -112,13 +122,14 @@ static int op_entry_read_opx(const char *path)
if(len > OP_LEN && rb->strcasecmp(&((path)[len-OP_LEN]), "." OP_EXT) == 0)
{
fd_opx = rb->open(path, O_RDONLY);
- if (fd_opx)
+ if (fd_opx >= 0)
{
filesize = rb->filesize(fd_opx);
ret = filesize;
if (filesize == op_entry_sz && !op_entry_read(fd_opx, 0, op_entry_sz))
ret = 0;
-
+ else if (op_entry_checksum() <= 0)
+ ret = 0;
rb->close(fd_opx);
}
}
@@ -131,7 +142,7 @@ static void op_entry_export(int selection)
int fd = -1;
char filename [MAX_PATH + 1];
- if (!op_entry_read(fd_dat, selection, op_entry_sz))
+ if (!op_entry_read(fd_dat, selection, op_entry_sz) || op_entry_checksum() <= 0)
goto failure;
rb->snprintf(filename, MAX_PATH, "%s/%s", PLUGIN_APPS_DIR, op_entry.name);
@@ -161,6 +172,11 @@ failure:
}
+static void op_entry_set_checksum(void)
+{
+ op_entry.checksum = open_plugin_csum;
+}
+
static void op_entry_set_name(void)
{
char tmp_buf[OPEN_PLUGIN_NAMESZ+1];
@@ -277,12 +293,12 @@ static int op_entry_transfer(int fd, int fd_tmp,
void *data)
{
int entries = -1;
- if (fd_tmp && fd && rb->lseek(fd, 0, SEEK_SET) == 0)
+ if (fd_tmp >= 0 && fd >= 0 && rb->lseek(fd, 0, SEEK_SET) == 0)
{
entries = 0;
while (rb->read(fd, &op_entry, op_entry_sz) == op_entry_sz)
{
- if (compfn && compfn(&op_entry, entries, data) > 0)
+ if (compfn && compfn(&op_entry, entries, data) > 0 && op_entry_checksum() > 0)
{
rb->write(fd_tmp, &op_entry, op_entry_sz);
entries++;
@@ -296,6 +312,7 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha
{
int len;
uint32_t hash;
+ uint32_t newhash;
char *pos = "";;
int fd_tmp = -1;
use_key = (use_key == true && key != NULL);
@@ -309,7 +326,8 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha
{
/* need to keep the old hash so we can remove the old entry */
hash = op_entry.hash;
- open_plugin_get_hash(plugin, &op_entry.hash);
+ open_plugin_get_hash(plugin, &newhash);
+ op_entry.hash = newhash;
}
else
hash = op_entry.hash;
@@ -352,9 +370,12 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha
/* hash on the parameter path if it is a file */
if (op_entry.lang_id <0 && key == op_entry.path &&
rb->file_exists(op_entry.param))
- open_plugin_get_hash(op_entry.path, &op_entry.hash);
+ {
+ open_plugin_get_hash(op_entry.path, &newhash);
+ op_entry.hash = newhash;
+ }
}
-
+ op_entry_set_checksum();
rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */
}
else if(op_entry_read_opx(plugin) == op_entry_sz)
@@ -369,13 +390,13 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha
open_plugin_get_hash(op_entry.path, &hash);
op_entry.hash = hash;
-
+ op_entry_set_checksum();
rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */
}
else
{
if (op_entry.lang_id != LANG_SHORTCUTS)
- rb->splashf(HZ / 2, rb->str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
+ rb->splashf(HZ * 2, rb->str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
return 0;
}
}
@@ -840,7 +861,7 @@ reopen_datfile:
synclist_set(MENU_ID_MAIN, selection, items, 1);
rb->gui_synclist_draw(&lists);
- while (!exit)
+ while (!exit && fd_dat >= 0)
{
action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
diff --git a/apps/plugins/pictureflow/pictureflow.c b/apps/plugins/pictureflow/pictureflow.c
index a170a57ec4..db04f6b89a 100644
--- a/apps/plugins/pictureflow/pictureflow.c
+++ b/apps/plugins/pictureflow/pictureflow.c
@@ -55,14 +55,21 @@ static fb_data *lcd_fb;
#define PF_BACK ACTION_STD_CANCEL
#define PF_MENU ACTION_STD_MENU
#define PF_WPS ACTION_TREE_WPS
+#define PF_JMP ACTION_LISTTREE_PGDOWN
+#define PF_JMP_PREV ACTION_LISTTREE_PGUP
#define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
#define PF_TRACKLIST (LAST_ACTION_PLACEHOLDER + 2)
#if defined(HAVE_SCROLLWHEEL) || CONFIG_KEYPAD == IRIVER_H10_PAD || \
CONFIG_KEYPAD == MPIO_HD300_PAD
+#if (CONFIG_KEYPAD != IPOD_1G2G_PAD) \
+ && (CONFIG_KEYPAD != IPOD_3G_PAD) \
+ && (CONFIG_KEYPAD != IPOD_4G_PAD) \
+ && (CONFIG_KEYPAD != FIIO_M3K_PAD)
#define USE_CORE_PREVNEXT
#endif
+#endif
#ifndef USE_CORE_PREVNEXT
/* scrollwheel targets use the wheel, just as they do in lists,
@@ -81,6 +88,18 @@ const struct button_mapping pf_context_album_scroll[] =
{PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
{PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
{PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
+#elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
+ || (CONFIG_KEYPAD == IPOD_3G_PAD) \
+ || (CONFIG_KEYPAD == IPOD_4G_PAD) \
+ || (CONFIG_KEYPAD == FIIO_M3K_PAD)
+ {PF_JMP_PREV, BUTTON_LEFT, BUTTON_NONE},
+ {PF_JMP_PREV, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
+ {PF_JMP, BUTTON_RIGHT, BUTTON_NONE},
+ {PF_JMP, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
+ {ACTION_NONE, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT},
+ {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
+ {ACTION_NONE, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_LEFT},
+ {ACTION_NONE, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_RIGHT},
#elif defined(BUTTON_LEFT) && defined(BUTTON_RIGHT)
{PF_PREV, BUTTON_LEFT, BUTTON_NONE},
{PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
@@ -137,7 +156,7 @@ const struct button_mapping pf_context_buttons[] =
{PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
#elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
{PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
-#elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD
+#elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD
{PF_QUIT, BUTTON_OFF, BUTTON_NONE},
#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
{PF_QUIT, BUTTON_REC, BUTTON_NONE},
@@ -451,6 +470,7 @@ static struct configdata config[] =
{ TYPE_INT, 0, 999999, { .int_p = &pf_cfg.last_album }, "last album", NULL },
{ TYPE_INT, 0, 1, { .int_p = &pf_cfg.backlight_mode }, "backlight", NULL },
{ TYPE_INT, 0, 999999, { .int_p = &aa_cache.idx }, "art cache pos", NULL },
+ { TYPE_INT, 0, 999999, { .int_p = &aa_cache.inspected }, "art cache inspected", NULL }
};
#define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
@@ -494,6 +514,15 @@ static struct pf_track_t pf_tracks;
void reset_track_list(void);
static bool thread_is_running;
+static bool wants_to_quit = false;
+
+/*
+ Prevent picture loading thread from allocating
+ buflib memory while the main thread may be
+ performing buffer-shifting operations.
+*/
+static struct mutex buf_ctx_mutex;
+static bool buf_ctx_locked = false;
static int cover_animation_keyframe;
static int extra_fade;
@@ -536,6 +565,18 @@ static void draw_progressbar(int step, int count, char *msg);
static void draw_splashscreen(unsigned char * buf_tmp, size_t buf_tmp_size);
static void free_all_slide_prio(int prio);
+static inline void buf_ctx_lock(void)
+{
+ rb->mutex_lock(&buf_ctx_mutex);
+ buf_ctx_locked = true;
+}
+
+static inline void buf_ctx_unlock(void)
+{
+ rb->mutex_unlock(&buf_ctx_mutex);
+ buf_ctx_locked = false;
+}
+
static bool check_database(bool prompt)
{
bool needwarn = true;
@@ -1056,8 +1097,8 @@ static int create_album_untagged(struct tagcache_search *tcs,
draw_splashscreen(*buf, *bufsz);
draw_progressbar(0, total_count, "Searching " UNTAGGED);
- /* search tagcache for all <untagged> albums & save the canonicalartist seek pos */
- if (rb->tagcache_search(tcs, tag_virt_canonicalartist))
+ /* search tagcache for all <untagged> albums & save the albumartist seek pos */
+ if (rb->tagcache_search(tcs, tag_albumartist))
{
rb->tagcache_search_add_filter(tcs, tag_album, pf_idx.album_untagged_seek);
@@ -1160,7 +1201,7 @@ static int build_artist_index(struct tagcache_search *tcs,
/* artist names starts at beginning of buf */
pf_idx.artist_names = *buf;
- rb->tagcache_search(tcs, tag_virt_canonicalartist);
+ rb->tagcache_search(tcs, tag_albumartist);
res = get_tcs_search_res(ePFS_ARTIST, tcs, &(*buf), bufsz);
rb->tagcache_search_finish(tcs);
if (res < SUCCESS)
@@ -1267,7 +1308,7 @@ static int create_album_index(void)
draw_progressbar(j, pf_idx.album_ct, NULL);
if (pf_idx.album_index[j].artist_seek >= 0) { continue; }
- rb->tagcache_search(&tcs, tag_virt_canonicalartist);
+ rb->tagcache_search(&tcs, tag_albumartist);
rb->tagcache_search_add_filter(&tcs, tag_album, pf_idx.album_index[j].seek);
last = 0;
@@ -1537,6 +1578,35 @@ static char* get_track_filename(const int track_index)
}
#endif
+
+
+static int get_album_artist_alpha_prev_index(void)
+{
+ char* current_album_artist = get_album_artist(center_index);
+ for (int i = center_index - 1; i > 0; i-- )
+ {
+ int artist_idx = pf_idx.album_index[i].artist_idx;
+ if(rb->strncmp(pf_idx.artist_names + artist_idx, current_album_artist, 1))
+ current_album_artist = pf_idx.artist_names + artist_idx;
+ while (i > 0 && !rb->strncmp(pf_idx.artist_names + pf_idx.album_index[i-1].artist_idx, current_album_artist, 1))
+ i--;
+ return i;
+ }
+ return 0;
+}
+
+static int get_album_artist_alpha_next_index(void)
+{
+ char* current_album_artist = get_album_artist(center_index);
+ for (int i = center_index + 1; i < pf_idx.album_ct; i++ )
+ {
+ int artist_idx = pf_idx.album_index[i].artist_idx;
+ if(rb->strncmp(pf_idx.artist_names + artist_idx, current_album_artist, 1))
+ return i;
+ }
+ return pf_idx.album_ct - 1;
+}
+
static int get_wps_current_index(void)
{
char* current_artist = UNTAGGED;
@@ -1589,6 +1659,7 @@ static int compare_tracks (const void *a_v, const void *b_v)
*/
static void create_track_index(const int slide_index)
{
+ buf_ctx_lock();
char temp[MAX_PATH + 1];
if ( slide_index == pf_tracks.cur_idx )
return;
@@ -1601,7 +1672,7 @@ static void create_track_index(const int slide_index)
if (pf_idx.album_index[slide_index].artist_idx >= 0)
{
- rb->tagcache_search_add_filter(&tcs, tag_virt_canonicalartist,
+ rb->tagcache_search_add_filter(&tcs, tag_albumartist,
pf_idx.album_index[slide_index].artist_seek);
}
@@ -1735,6 +1806,18 @@ fail:
}
/**
+ Re-grow the buflib buffer by returning space borrowed
+ for track list
+*/
+static inline void free_borrowed_tracks(void)
+{
+ rb->buflib_buffer_in(&buf_ctx, pf_tracks.borrowed);
+ pf_tracks.borrowed = 0;
+ pf_tracks.cur_idx = -1;
+ buf_ctx_unlock();
+}
+
+/**
Determine filename of the album art for the given slide_index and
store the result in buf.
The algorithm looks for the first track of the given album uses
@@ -1756,7 +1839,7 @@ static bool get_albumart_for_index_from_db(const int slide_index, char *buf,
rb->tagcache_search_add_filter(&tcs, tag_album,
pf_idx.album_index[slide_index].seek);
- rb->tagcache_search_add_filter(&tcs, tag_virt_canonicalartist,
+ rb->tagcache_search_add_filter(&tcs, tag_albumartist,
pf_idx.album_index[slide_index].artist_seek);
if ( rb->tagcache_get_next(&tcs) ) {
@@ -2002,6 +2085,7 @@ static bool create_albumart_cache(void)
{
draw_splashscreen(pf_idx.buf, pf_idx.buf_sz);
draw_progressbar(0, pf_idx.album_ct, "Preparing artwork");
+ aa_cache.inspected = 0;
for (int i=0; i < pf_idx.album_ct; i++)
{
incremental_albumart_cache(true);
@@ -2114,6 +2198,26 @@ static bool create_pf_thread(void)
}
+static void initialize_slide_cache(void)
+{
+ int i= 0;
+ for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
+ pf_sldcache.cache[i].hid = 0;
+ pf_sldcache.cache[i].index = 0;
+ pf_sldcache.cache[i].next = i + 1;
+ pf_sldcache.cache[i].prev = i - 1;
+ }
+ pf_sldcache.cache[0].prev = i - 1;
+ pf_sldcache.cache[i - 1].next = 0;
+
+ pf_sldcache.free = 0;
+ pf_sldcache.used = -1;
+ pf_sldcache.left_idx = -1;
+ pf_sldcache.right_idx = -1;
+ pf_sldcache.center_idx = -1;
+}
+
+
/*
* The following functions implement the linked-list-in-array used to manage
* the LRU cache of slides, and the list of free cache slots.
@@ -2122,20 +2226,30 @@ static bool create_pf_thread(void)
#define _SEEK_RIGHT_WHILE(start, cond) \
({ \
int ind_, next_ = (start); \
+ int i_ = 0; \
do { \
ind_ = next_; \
next_ = pf_sldcache.cache[ind_].next; \
- } while (next_ != pf_sldcache.used && (cond)); \
+ i_++; \
+ } while (next_ != pf_sldcache.used && (cond) && i_ < SLIDE_CACHE_SIZE); \
+ if (i_ >= SLIDE_CACHE_SIZE) \
+ /* TODO: Not supposed to happen */ \
+ ind_ = -1; \
ind_; \
})
#define _SEEK_LEFT_WHILE(start, cond) \
({ \
int ind_, next_ = (start); \
+ int i_ = 0; \
do { \
ind_ = next_; \
next_ = pf_sldcache.cache[ind_].prev; \
- } while (ind_ != pf_sldcache.used && (cond)); \
+ i_++; \
+ } while (ind_ != pf_sldcache.used && (cond) && i_ < SLIDE_CACHE_SIZE); \
+ if (i_ >= SLIDE_CACHE_SIZE) \
+ /* TODO: Not supposed to happen */ \
+ ind_ = -1; \
ind_; \
})
@@ -2324,12 +2438,7 @@ static int read_pfraw(char* filename, int prio)
bm->height = bmph.height;
pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
- int y;
- for( y = 0; y < bm->height; y++ )
- {
- rb->read( fh, data , sizeof( pix_t ) * bm->width );
- data += bm->width;
- }
+ rb->read( fh, data , sizeof( pix_t ) * bm->width * bm->height );
rb->close( fh );
return hid;
}
@@ -2369,6 +2478,10 @@ static inline bool load_and_prepare_surface(const int slide_index,
*/
bool load_new_slide(void)
{
+ buf_ctx_lock();
+ if (wants_to_quit)
+ return false;
+
int i = -1;
if (pf_sldcache.center_idx != -1)
@@ -2380,6 +2493,8 @@ bool load_new_slide(void)
{
pf_sldcache.center_idx = _SEEK_RIGHT_WHILE(pf_sldcache.center_idx,
pf_sldcache.cache[next_].index <= center_index);
+ if (pf_sldcache.center_idx == -1)
+ goto fatal_fail;
prev = pf_sldcache.center_idx;
next = pf_sldcache.cache[pf_sldcache.center_idx].next;
@@ -2388,6 +2503,8 @@ bool load_new_slide(void)
{
pf_sldcache.center_idx = _SEEK_LEFT_WHILE(pf_sldcache.center_idx,
pf_sldcache.cache[next_].index >= center_index);
+ if (pf_sldcache.center_idx == -1)
+ goto fatal_fail;
next = pf_sldcache.center_idx;
prev = pf_sldcache.cache[pf_sldcache.center_idx].prev;
@@ -2415,6 +2532,7 @@ bool load_new_slide(void)
pf_sldcache.center_idx = i;
pf_sldcache.left_idx = i;
pf_sldcache.right_idx = i;
+ buf_ctx_unlock();
return true;
}
}
@@ -2433,6 +2551,8 @@ bool load_new_slide(void)
pf_sldcache.right_idx = _SEEK_RIGHT_WHILE(pf_sldcache.right_idx,
pf_sldcache.cache[ind_].index - 1 == pf_sldcache.cache[next_].index);
+ if (pf_sldcache.right_idx == -1 || pf_sldcache.left_idx == -1)
+ goto fatal_fail;
/* update indices */
@@ -2445,25 +2565,33 @@ bool load_new_slide(void)
if ((prio_l < prio_r || right >= number_of_slides) && left > 0)
{
if (pf_sldcache.free == -1 && !free_slide_prio(prio_l))
+ {
+ buf_ctx_unlock();
return false;
+ }
i = lla_pop_head(&pf_sldcache.free);
if (load_and_prepare_surface(left - 1, i, prio_l))
{
lla_insert_before(&pf_sldcache.used, i, pf_sldcache.left_idx);
pf_sldcache.left_idx = i;
+ buf_ctx_unlock();
return true;
}
} else if(right < number_of_slides - 1)
{
if (pf_sldcache.free == -1 && !free_slide_prio(prio_r))
+ {
+ buf_ctx_unlock();
return false;
+ }
i = lla_pop_head(&pf_sldcache.free);
if (load_and_prepare_surface(right + 1, i, prio_r))
{
lla_insert_after(i, pf_sldcache.right_idx);
pf_sldcache.right_idx = i;
+ buf_ctx_unlock();
return true;
}
}
@@ -2478,6 +2606,7 @@ insert_first_slide:
pf_sldcache.left_idx = i;
pf_sldcache.right_idx = i;
pf_sldcache.used = i;
+ buf_ctx_unlock();
return true;
}
}
@@ -2486,6 +2615,12 @@ fail_and_refree:
{
lla_insert_tail(&pf_sldcache.free, i);
}
+ buf_ctx_unlock();
+ return false;
+fatal_fail:
+ free_all_slide_prio(0);
+ initialize_slide_cache();
+ buf_ctx_unlock();
return false;
}
@@ -2518,11 +2653,13 @@ static inline struct dim *surface(const int slide_index)
int i;
if ((i = pf_sldcache.used ) != -1)
{
+ int j = 0;
do {
if (pf_sldcache.cache[i].index == slide_index)
return get_slide(pf_sldcache.cache[i].hid);
i = pf_sldcache.cache[i].next;
- } while (i != pf_sldcache.used);
+ j++;
+ } while (i != pf_sldcache.used && j < SLIDE_CACHE_SIZE);
}
return get_slide(empty_slide_hid);
}
@@ -3002,6 +3139,10 @@ static void update_scroll_animation(void)
*/
static void cleanup(void)
{
+ wants_to_quit = true;
+ if (buf_ctx_locked)
+ buf_ctx_unlock();
+
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
@@ -3172,7 +3313,7 @@ static int main_menu(void)
return -2;
#if PF_PLAYBACK_CAPABLE
case PF_MENU_CLEAR_PLAYLIST:
- if(rb->playlist_remove_all_tracks(NULL) == 0) {
+ if(rb->warn_on_pl_erase() && rb->playlist_remove_all_tracks(NULL) == 0) {
rb->playlist_create(NULL, NULL);
rb->splash(HZ*2, ID2P(LANG_PLAYLIST_CLEARED));
}
@@ -3240,6 +3381,47 @@ static void update_cover_out_animation(void)
}
/**
+ Skip steps for zooming into the current cover
+*/
+static void interrupt_cover_in_animation(void)
+{
+ pf_state = pf_show_tracks;
+ cover_animation_keyframe = 0;
+ extra_fade = 13 * 19;
+ center_slide.distance = -5 * 19;
+ center_slide.angle = 19 + (15 * 16);
+}
+
+/**
+ Skip steps for zooming out the current cover
+*/
+static void interrupt_cover_out_animation(void)
+{
+ pf_state = pf_idle;
+ cover_animation_keyframe = 0;
+ extra_fade = 0;
+ set_current_slide(center_index);
+}
+
+/**
+ Stop zooming out the current cover and start zooming in
+*/
+static void revert_cover_out_animation(void)
+{
+ pf_state = pf_cover_in;
+ cover_animation_keyframe = 34 - cover_animation_keyframe;
+}
+
+/**
+ Stop zooming into the current cover and start zooming out
+*/
+static void revert_cover_in_animation(void)
+{
+ pf_state = pf_cover_out;
+ cover_animation_keyframe = 34 - cover_animation_keyframe;
+}
+
+/**
Draw a blue gradient at y with height h
*/
static inline void draw_gradient(int y, int h)
@@ -3368,7 +3550,7 @@ static void select_next_track(void)
pf_tracks.sel++;
if (pf_tracks.sel==(pf_tracks.list_visible+pf_tracks.list_start))
pf_tracks.list_start++;
- } else {
+ } else if (rb->global_settings->list_wraparound) {
/* Rollover */
pf_tracks.sel = 0;
pf_tracks.list_start = 0;
@@ -3380,7 +3562,7 @@ static void select_prev_track(void)
if (pf_tracks.sel > 0 ) {
if (pf_tracks.sel==pf_tracks.list_start) pf_tracks.list_start--;
pf_tracks.sel--;
- } else {
+ } else if (rb->global_settings->list_wraparound) {
/* Rolllover */
pf_tracks.sel = pf_tracks.count - 1;
pf_tracks.list_start = pf_tracks.count - pf_tracks.list_visible;
@@ -3388,6 +3570,19 @@ static void select_prev_track(void)
}
#if PF_PLAYBACK_CAPABLE
+
+static bool pf_warn_on_pl_erase(void)
+{
+#ifdef USEGSLIB
+ grey_show(false);
+#endif
+ bool ret = rb->warn_on_pl_erase();
+#ifdef USEGSLIB
+ grey_show(true);
+#endif
+ return ret;
+}
+
/*
* Puts the current tracklist into a newly created playlist and starts playling
*/
@@ -3420,15 +3615,21 @@ static void start_playback(bool append)
else
return;
- if (rb->global_settings->playlist_shuffle)
+ if (!append && rb->global_settings->playlist_shuffle)
position = rb->playlist_shuffle(*rb->current_tick, pf_tracks.sel);
play:
/* TODO: can we adjust selected_track if !play_selected ?
* if shuffle, we can't predict the playing track easily, and for either
* case the track list doesn't get auto scrolled*/
if(!append)
+ {
rb->playlist_start(position, 0, 0);
- old_playlist = center_slide.slide_index;
+ /* make warn on playlist erase work */
+ rb->playlist_get_current()->num_inserted_tracks = 0;
+ old_playlist = center_slide.slide_index;
+ }
+ else
+ old_playlist = -1;
old_shuffle = shuffle;
}
#endif
@@ -3509,6 +3710,28 @@ static void draw_album_text(void)
}
}
+#if PF_PLAYBACK_CAPABLE
+/**
+ Display an info message when items have been added to playlist
+*/
+static void rb_splash_added_to_playlist(void)
+{
+#ifdef USEGSLIB
+ grey_show(false);
+#if LCD_DEPTH > 1
+ rb->lcd_set_background(N_BRIGHT(0));
+ rb->lcd_set_foreground(N_BRIGHT(255));
+#endif
+ rb->lcd_clear_display();
+ rb->lcd_update();
+#endif
+ rb->splash(HZ*2, ID2P(LANG_ADDED_TO_PLAYLIST));
+#ifdef USEGSLIB
+ grey_show(true);
+#endif
+}
+#endif
+
/**
Display an error message and wait for input.
*/
@@ -3550,6 +3773,8 @@ static int pictureflow_main(void)
#endif
}
+ rb->mutex_init(&buf_ctx_mutex);
+
init_scroll_lines();
init_reflect_table();
@@ -3628,23 +3853,7 @@ static int pictureflow_main(void)
return PLUGIN_ERROR;
}
- int i;
-
- /* initialize */
- for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
- pf_sldcache.cache[i].hid = 0;
- pf_sldcache.cache[i].index = 0;
- pf_sldcache.cache[i].next = i + 1;
- pf_sldcache.cache[i].prev = i - 1;
- }
- pf_sldcache.cache[0].prev = i - 1;
- pf_sldcache.cache[i - 1].next = 0;
-
- pf_sldcache.free = 0;
- pf_sldcache.used = -1;
- pf_sldcache.left_idx = -1;
- pf_sldcache.right_idx = -1;
- pf_sldcache.center_idx = -1;
+ initialize_slide_cache();
buffer = LCD_BUF;
@@ -3757,12 +3966,14 @@ static int pictureflow_main(void)
case PF_BACK:
if ( pf_state == pf_show_tracks )
{
- rb->buflib_buffer_in(&buf_ctx, pf_tracks.borrowed);
- pf_tracks.borrowed = 0;
- pf_tracks.cur_idx = -1;
pf_state = pf_cover_out;
+ free_borrowed_tracks();
}
- if (pf_state == pf_idle || pf_state == pf_scrolling)
+ else if (pf_state == pf_cover_in)
+ revert_cover_in_animation();
+ else if (pf_state == pf_cover_out)
+ interrupt_cover_out_animation();
+ else if (pf_state == pf_idle || pf_state == pf_scrolling)
return PLUGIN_OK;
break;
case PF_MENU:
@@ -3783,6 +3994,11 @@ static int pictureflow_main(void)
case PF_NEXT_REPEAT:
if ( pf_state == pf_show_tracks )
select_next_track();
+ else if (pf_state == pf_cover_in)
+ interrupt_cover_in_animation();
+ else if (pf_state == pf_cover_out)
+ interrupt_cover_out_animation();
+
if ( pf_state == pf_idle || pf_state == pf_scrolling )
show_next_slide();
break;
@@ -3791,24 +4007,54 @@ static int pictureflow_main(void)
case PF_PREV_REPEAT:
if ( pf_state == pf_show_tracks )
select_prev_track();
+ else if (pf_state == pf_cover_in)
+ interrupt_cover_in_animation();
+ else if (pf_state == pf_cover_out)
+ interrupt_cover_out_animation();
+
if ( pf_state == pf_idle || pf_state == pf_scrolling )
show_previous_slide();
break;
+ case PF_JMP:
+ if (pf_state == pf_idle || pf_state == pf_scrolling)
+ {
+ pf_state = pf_idle;
+ set_current_slide(get_album_artist_alpha_next_index());
+ }
+ break;
+ case PF_JMP_PREV:
+ if (pf_state == pf_idle || pf_state == pf_scrolling)
+ {
+ pf_state = pf_idle;
+ set_current_slide(get_album_artist_alpha_prev_index());
+ }
+ break;
#if PF_PLAYBACK_CAPABLE
case PF_CONTEXT:
- if ( pf_cfg.auto_wps != 0 ) {
+ if (pf_cfg.auto_wps != 0 &&
+ (pf_state == pf_idle || pf_state == pf_scrolling ||
+ pf_state == pf_show_tracks || pf_state == pf_cover_out)) {
+
+ if ( pf_state == pf_scrolling)
+ {
+ set_current_slide(target);
+ pf_state = pf_idle;
+ } else if (pf_state == pf_cover_out)
+ interrupt_cover_out_animation();
+
if( pf_state == pf_idle ) {
create_track_index(center_slide.slide_index);
reset_track_list();
start_playback(true);
- rb->splash(HZ*2, ID2P(LANG_ADDED_TO_PLAYLIST));
+ free_borrowed_tracks();
}
- else if( pf_state == pf_show_tracks ) {
+ else
+ {
rb->playlist_insert_track(NULL, get_track_filename(pf_tracks.sel),
PLAYLIST_INSERT_LAST, false, true);
rb->playlist_sync(NULL);
- rb->splash(HZ*2, ID2P(LANG_ADDED_TO_PLAYLIST));
}
+ rb_splash_added_to_playlist();
}
break;
#endif
@@ -3818,9 +4064,13 @@ static int pictureflow_main(void)
break;
}
case PF_SELECT:
- if ( pf_state == pf_idle ) {
+ if ( pf_state == pf_idle || pf_state == pf_scrolling) {
+ if (pf_state == pf_scrolling)
+ set_current_slide(target);
#if PF_PLAYBACK_CAPABLE
if(pf_cfg.auto_wps == 1) {
+ if (!pf_warn_on_pl_erase())
+ break;
create_track_index(center_slide.slide_index);
reset_track_list();
start_playback(false);
@@ -3831,15 +4081,19 @@ static int pictureflow_main(void)
#endif
pf_state = pf_cover_in;
}
- else if ( pf_state == pf_show_tracks ) {
+ else if (pf_state == pf_cover_out)
+ revert_cover_out_animation();
+ else if (pf_state == pf_cover_in)
+ interrupt_cover_in_animation();
#if PF_PLAYBACK_CAPABLE
+ else if (pf_state == pf_show_tracks && pf_warn_on_pl_erase()) {
start_playback(false);
if(pf_cfg.auto_wps != 0) {
pf_cfg.last_album = center_index;
return PLUGIN_GOTO_WPS;
}
-#endif
}
+#endif
break;
default:
exit_on_usb(button);
diff --git a/apps/plugins/pitch_screen.c b/apps/plugins/pitch_screen.c
new file mode 100644
index 0000000000..e24e0240a2
--- /dev/null
+++ b/apps/plugins/pitch_screen.c
@@ -0,0 +1,1279 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 Björn Stenberg
+ *
+ *
+ * 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/icon_helper.h"
+#include "lib/arg_helper.h"
+
+#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */
+ /* on both sides when drawing */
+
+#define PITCH_MAX (200 * PITCH_SPEED_PRECISION)
+#define PITCH_MIN (50 * PITCH_SPEED_PRECISION)
+#define PITCH_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
+#define PITCH_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
+#define PITCH_NUDGE_DELTA (2 * PITCH_SPEED_PRECISION) /* 2% */
+
+#define SPEED_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
+#define SPEED_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
+
+#define SEMITONE_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* 10 cents */
+#define SEMITONE_BIG_DELTA PITCH_SPEED_PRECISION /* 1 semitone */
+
+#define PVAR_VERBOSE 0x01
+#define PVAR_GUI 0x02
+struct pvars
+{
+ int32_t speed;
+ int32_t pitch;
+ int32_t stretch;
+ int32_t flags;
+};
+static struct pvars pitch_vars;
+
+enum
+{
+ PITCH_TOP = 0,
+ PITCH_MID,
+ PITCH_BOTTOM,
+ PITCH_ITEM_COUNT,
+};
+
+/* This is a table of semitone percentage values of the appropriate
+ precision (based on PITCH_SPEED_PRECISION). Note that these are
+ all constant expressions, which will be evaluated at compile time,
+ so no need to worry about how complex the expressions look.
+ That's just to get the precision right.
+
+ I calculated these values, starting from 50, as
+
+ x(n) = 50 * 2^(n/12)
+
+ All that math in each entry simply converts the float constant
+ to an integer equal to PITCH_SPEED_PRECISION times the float value,
+ with as little precision loss as possible (i.e. correctly rounding
+ the last digit).
+*/
+#define TO_INT_WITH_PRECISION(x) \
+ ( (unsigned short)(((x) * PITCH_SPEED_PRECISION * 10 + 5) / 10) )
+
+static const unsigned short semitone_table[] =
+{
+ TO_INT_WITH_PRECISION(50.00000000), /* Octave lower */
+ TO_INT_WITH_PRECISION(52.97315472),
+ TO_INT_WITH_PRECISION(56.12310242),
+ TO_INT_WITH_PRECISION(59.46035575),
+ TO_INT_WITH_PRECISION(62.99605249),
+ TO_INT_WITH_PRECISION(66.74199271),
+ TO_INT_WITH_PRECISION(70.71067812),
+ TO_INT_WITH_PRECISION(74.91535384),
+ TO_INT_WITH_PRECISION(79.37005260),
+ TO_INT_WITH_PRECISION(84.08964153),
+ TO_INT_WITH_PRECISION(89.08987181),
+ TO_INT_WITH_PRECISION(94.38743127),
+ TO_INT_WITH_PRECISION(100.0000000), /* Normal sound */
+ TO_INT_WITH_PRECISION(105.9463094),
+ TO_INT_WITH_PRECISION(112.2462048),
+ TO_INT_WITH_PRECISION(118.9207115),
+ TO_INT_WITH_PRECISION(125.9921049),
+ TO_INT_WITH_PRECISION(133.4839854),
+ TO_INT_WITH_PRECISION(141.4213562),
+ TO_INT_WITH_PRECISION(149.8307077),
+ TO_INT_WITH_PRECISION(158.7401052),
+ TO_INT_WITH_PRECISION(168.1792831),
+ TO_INT_WITH_PRECISION(178.1797436),
+ TO_INT_WITH_PRECISION(188.7748625),
+ TO_INT_WITH_PRECISION(200.0000000) /* Octave higher */
+};
+
+#define NUM_SEMITONES ((int)(sizeof(semitone_table)/sizeof(semitone_table[0])))
+#define SEMITONE_END (NUM_SEMITONES/2)
+#define SEMITONE_START (-SEMITONE_END)
+
+/* A table of values for approximating the cent curve with
+ linear interpolation. Multipy the next lowest semitone
+ by this much to find the corresponding cent percentage.
+
+ These values were calculated as
+ x(n) = 100 * 2^(n * 20/1200)
+*/
+
+static const unsigned short cent_interp[] =
+{
+ TO_INT_WITH_PRECISION(100.0000000),
+ TO_INT_WITH_PRECISION(101.1619440),
+ TO_INT_WITH_PRECISION(102.3373892),
+ TO_INT_WITH_PRECISION(103.5264924),
+ TO_INT_WITH_PRECISION(104.7294123),
+ /* this one's the next semitone but we have it here for convenience */
+ TO_INT_WITH_PRECISION(105.9463094),
+};
+
+int viewport_get_nb_lines(const struct viewport *vp)
+{
+ return vp->height/rb->font_get(vp->font)->height;
+}
+#if 0 /* replaced with cbmp_get_icon(CBMP_Mono_7x8, Icon_ABCD, &w, &h) */
+enum icons_7x8 {
+ Icon_Plug,
+ Icon_USBPlug,
+ Icon_Mute,
+ Icon_Play,
+ Icon_Stop,
+ Icon_Pause,
+ Icon_FastForward,
+ Icon_FastBackward,
+ Icon_Record,
+ Icon_RecPause,
+ Icon_Radio,
+ Icon_Radio_Mute,
+ Icon_Repeat,
+ Icon_RepeatOne,
+ Icon_Shuffle,
+ Icon_DownArrow,
+ Icon_UpArrow,
+ Icon_RepeatAB,
+ Icon7x8Last
+};
+
+const unsigned char bitmap_icons_7x8[][7] =
+{
+ {0x08,0x1c,0x3e,0x3e,0x3e,0x14,0x14}, /* Power plug */
+ {0x1c,0x14,0x3e,0x2a,0x22,0x1c,0x08}, /* USB plug */
+ {0x01,0x1e,0x1c,0x3e,0x7f,0x20,0x40}, /* Speaker mute */
+ {0x00,0x7f,0x7f,0x3e,0x1c,0x08,0x00}, /* Play */
+ {0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f}, /* Stop */
+ {0x00,0x7f,0x7f,0x00,0x7f,0x7f,0x00}, /* Pause */
+ {0x7f,0x3e,0x1c,0x7f,0x3e,0x1c,0x08}, /* Fast forward */
+ {0x08,0x1c,0x3e,0x7f,0x1c,0x3e,0x7f}, /* Fast backward */
+ {0x1c,0x3e,0x7f,0x7f,0x7f,0x3e,0x1c}, /* Record */
+ {0x1c,0x3e,0x7f,0x00,0x7f,0x3e,0x1c}, /* Record pause */
+ {0x40,0xa0,0xa0,0xa0,0x7f,0x02,0x02}, /* Radio on */
+ {0x42,0xa4,0xa8,0xb0,0x7f,0x22,0x42}, /* Radio mute */
+ {0x44,0x4e,0x5f,0x44,0x44,0x44,0x38}, /* Repeat playmode */
+ {0x44,0x4e,0x5f,0x44,0x38,0x02,0x7f}, /* Repeat-one playmode */
+ {0x3e,0x41,0x51,0x41,0x45,0x41,0x3e}, /* Shuffle playmode (dice) */
+ {0x04,0x0c,0x1c,0x3c,0x1c,0x0c,0x04}, /* Down-arrow */
+ {0x20,0x30,0x38,0x3c,0x38,0x30,0x20}, /* Up-arrow */
+ {0x7f,0x04,0x4e,0x5f,0x44,0x38,0x7f} /* Repeat-AB playmode */
+};
+#endif
+
+/* Number of cents between entries in the cent_interp table */
+#define CENT_INTERP_INTERVAL 20
+#define CENT_INTERP_NUM ((int)(sizeof(cent_interp)/sizeof(cent_interp[0])))
+
+/* This stores whether the pitch and speed are at their own limits */
+/* or that of the timestretching algorithm */
+static bool at_limit = false;
+
+/*
+ *
+ * The pitchscreen is divided into 3 viewports (each row is a viewport)
+ * Then each viewport is again divided into 3 colums, each showsing some infos
+ * Additionally, on touchscreen, each cell represents a button
+ *
+ * Below a sketch describing what each cell will show (what's drawn on it)
+ * --------------------------
+ * | | | | <-- pitch up in the middle (text and button)
+ * | | | | <-- arrows for mode toggling on the sides for touchscreen
+ * |------------------------|
+ * | | | | <-- semitone/speed up/down on the sides
+ * | | | | <-- reset pitch&speed in the middle
+ * |------------------------|
+ * | | | | <-- pitch down in the middle
+ * | | | | <-- Two "OK" for exit on the sides for touchscreen
+ * |------------------------|
+ *
+ *
+ */
+
+static void speak_pitch_mode(bool enqueue)
+{
+ bool timestretch_mode = rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available();
+ if (timestretch_mode)
+ rb->talk_id(VOICE_PITCH_TIMESTRETCH_MODE, enqueue);
+ if (rb->global_settings->pitch_mode_semitone)
+ rb->talk_id(VOICE_PITCH_SEMITONE_MODE, timestretch_mode ? true : enqueue);
+ else
+ rb->talk_id(VOICE_PITCH_ABSOLUTE_MODE, timestretch_mode ? true : enqueue);
+ return;
+}
+
+/*
+ * Fixes the viewports so they represent the 3 rows, and adds a little margin
+ * on all sides for the icons (which are drawn outside of the grid
+ *
+ * The modified viewports need to be passed to the touchscreen handling function
+ **/
+static void pitchscreen_fix_viewports(struct viewport *parent,
+ struct viewport pitch_viewports[PITCH_ITEM_COUNT])
+{
+ int i, font_height;
+ font_height = rb->font_get(parent->font)->height;
+ for (i = 0; i < PITCH_ITEM_COUNT; i++)
+ {
+ pitch_viewports[i] = *parent;
+ pitch_viewports[i].height = parent->height / PITCH_ITEM_COUNT;
+ pitch_viewports[i].x += ICON_BORDER;
+ pitch_viewports[i].width -= 2*ICON_BORDER;
+ }
+ pitch_viewports[PITCH_TOP].y += ICON_BORDER;
+ pitch_viewports[PITCH_TOP].height -= ICON_BORDER;
+
+ if(pitch_viewports[PITCH_MID].height < font_height * 2)
+ pitch_viewports[PITCH_MID].height = font_height * 2;
+
+ pitch_viewports[PITCH_MID].y = pitch_viewports[PITCH_TOP].y
+ + pitch_viewports[PITCH_TOP].height;
+
+ pitch_viewports[PITCH_BOTTOM].y = pitch_viewports[PITCH_MID].y
+ + pitch_viewports[PITCH_MID].height;
+
+ pitch_viewports[PITCH_BOTTOM].height -= ICON_BORDER;
+}
+
+/* must be called before pitchscreen_draw, or within
+ * since it neither clears nor updates the display */
+static void pitchscreen_draw_icons(struct screen *display,
+ struct viewport *parent)
+{
+
+ display->set_viewport(parent);
+ int w, h;
+ const unsigned char* uparrow = cbmp_get_icon(CBMP_Mono_7x8, Icon_UpArrow, &w, &h);
+ if (uparrow)
+ display->mono_bitmap(uparrow, parent->width/2 - 3, 2, w, h);
+
+ const unsigned char* dnarrow = cbmp_get_icon(CBMP_Mono_7x8, Icon_DownArrow, &w, &h);
+ if (dnarrow)
+ display->mono_bitmap(dnarrow, parent->width /2 - 3, parent->height - 10, w, h);
+
+ const unsigned char* fastfwd = cbmp_get_icon(CBMP_Mono_7x8, Icon_FastForward, &w, &h);
+ if (fastfwd)
+ display->mono_bitmap(fastfwd, parent->width - 10, parent->height /2 - 4, 7, 8);
+
+ const unsigned char* fastrew = cbmp_get_icon(CBMP_Mono_7x8, Icon_FastBackward, &w, &h);
+ if (fastrew)
+ display->mono_bitmap(fastrew, 2, parent->height /2 - 4, w, h);
+
+ display->update_viewport();
+
+}
+
+static void pitchscreen_draw(struct screen *display, int max_lines,
+ struct viewport pitch_viewports[PITCH_ITEM_COUNT],
+ int32_t pitch, int32_t semitone
+ ,int32_t speed
+ )
+{
+ const char* ptr;
+ char buf[32];
+ int w, h;
+ bool show_lang_pitch;
+ struct viewport *last_vp = NULL;
+
+ /* "Pitch up/Pitch down" - hide for a small screen,
+ * the text is drawn centered automatically
+ *
+ * note: this assumes 5 lines always fit on a touchscreen (should be
+ * reasonable) */
+ if (max_lines >= 5)
+ {
+ int w, h;
+ struct viewport *vp = &pitch_viewports[PITCH_TOP];
+ last_vp = display->set_viewport(vp);
+ display->clear_viewport();
+#ifdef HAVE_TOUCHSCREEN
+ /* two arrows in the top row, left and right column */
+ char *arrows[] = { "<", ">"};
+ display->getstringsize(arrows[0], &w, &h);
+ display->putsxy(0, vp->height/2 - h/2, arrows[0]);
+ display->putsxy(vp->width - w, vp->height/2 - h/2, arrows[1]);
+#endif
+ /* UP: Pitch Up */
+ if (rb->global_settings->pitch_mode_semitone)
+ ptr = rb->str(LANG_PITCH_UP_SEMITONE);
+ else
+ ptr = rb->str(LANG_PITCH_UP);
+
+ display->getstringsize(ptr, &w, NULL);
+ /* draw text */
+ display->putsxy(vp->width/2 - w/2, 0, ptr);
+ display->update_viewport();
+
+ /* DOWN: Pitch Down */
+ vp = &pitch_viewports[PITCH_BOTTOM];
+ display->set_viewport(vp);
+ display->clear_viewport();
+
+#ifdef HAVE_TOUCHSCREEN
+ ptr = rb->str(LANG_KBD_OK);
+ display->getstringsize(ptr, &w, &h);
+ /* one OK in the middle first column of the vp (at half height) */
+ display->putsxy(vp->width/6 - w/2, vp->height/2 - h/2, ptr);
+ /* one OK in the middle of the last column of the vp (at half height) */
+ display->putsxy(5*vp->width/6 - w/2, vp->height/2 - h/2, ptr);
+#endif
+ if (rb->global_settings->pitch_mode_semitone)
+ ptr = rb->str(LANG_PITCH_DOWN_SEMITONE);
+ else
+ ptr = rb->str(LANG_PITCH_DOWN);
+ display->getstringsize(ptr, &w, &h);
+ /* draw text */
+ display->putsxy(vp->width/2 - w/2, vp->height - h, ptr);
+ display->update_viewport();
+ }
+
+ /* Middle section */
+ display->set_viewport(&pitch_viewports[PITCH_MID]);
+ display->clear_viewport();
+ int width_used = 0;
+
+ /* Middle section upper line - hide for a small screen */
+ if ((show_lang_pitch = (max_lines >= 3)))
+ {
+ if(rb->global_settings->pitch_mode_timestretch)
+ {
+ /* Pitch:XXX.X% */
+ if(rb->global_settings->pitch_mode_semitone)
+ {
+ rb->snprintf(buf, sizeof(buf), "%s: %s%d.%02d", rb->str(LANG_PITCH),
+ semitone >= 0 ? "+" : "-",
+ abs(semitone / PITCH_SPEED_PRECISION),
+ abs((semitone % PITCH_SPEED_PRECISION) /
+ (PITCH_SPEED_PRECISION / 100))
+ );
+ }
+ else
+ {
+ rb->snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", rb->str(LANG_PITCH),
+ pitch / PITCH_SPEED_PRECISION,
+ (pitch % PITCH_SPEED_PRECISION) /
+ (PITCH_SPEED_PRECISION / 10));
+ }
+ }
+ else
+ {
+ /* Rate */
+ rb->snprintf(buf, sizeof(buf), "%s:", rb->str(LANG_PLAYBACK_RATE));
+ }
+ display->getstringsize(buf, &w, &h);
+ display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
+ (pitch_viewports[PITCH_MID].height / 2) - h, buf);
+ if (w > width_used)
+ width_used = w;
+ }
+
+ /* Middle section lower line */
+ /* "Speed:XXX%" */
+ if(rb->global_settings->pitch_mode_timestretch)
+ {
+ rb->snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", rb->str(LANG_SPEED),
+ speed / PITCH_SPEED_PRECISION,
+ (speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
+ }
+ else
+ {
+ if(rb->global_settings->pitch_mode_semitone)
+ {
+ rb->snprintf(buf, sizeof(buf), "%s%d.%02d",
+ semitone >= 0 ? "+" : "-",
+ abs(semitone / PITCH_SPEED_PRECISION),
+ abs((semitone % PITCH_SPEED_PRECISION) /
+ (PITCH_SPEED_PRECISION / 100))
+ );
+ }
+ else
+ {
+ rb->snprintf(buf, sizeof(buf), "%ld.%ld%%",
+ pitch / PITCH_SPEED_PRECISION,
+ (pitch % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
+ }
+ }
+
+ display->getstringsize(buf, &w, &h);
+ display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
+ show_lang_pitch ? (pitch_viewports[PITCH_MID].height / 2) :
+ (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
+ buf);
+ if (w > width_used)
+ width_used = w;
+
+ /* "limit" and "timestretch" labels */
+ if (max_lines >= 7)
+ {
+ if(at_limit)
+ {
+ const char * const p = rb->str(LANG_STRETCH_LIMIT);
+ display->getstringsize(p, &w, &h);
+ display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
+ (pitch_viewports[PITCH_MID].height / 2) + h, p);
+ if (w > width_used)
+ width_used = w;
+ }
+ }
+
+ /* Middle section left/right labels */
+ const char *leftlabel = "-2%";
+ const char *rightlabel = "+2%";
+ if (rb->global_settings->pitch_mode_timestretch)
+ {
+ leftlabel = "<<";
+ rightlabel = ">>";
+ }
+
+ /* Only display if they fit */
+ display->getstringsize(leftlabel, &w, &h);
+ width_used += w;
+ display->getstringsize(rightlabel, &w, &h);
+ width_used += w;
+
+ if (width_used <= pitch_viewports[PITCH_MID].width)
+ {
+ display->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
+ leftlabel);
+ display->putsxy((pitch_viewports[PITCH_MID].width - w),
+ (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
+ rightlabel);
+ }
+ display->update_viewport();
+ display->set_viewport(last_vp);
+}
+
+static int32_t pitch_increase(int32_t pitch, int32_t pitch_delta, bool allow_cutoff
+ /* need this to maintain correct pitch/speed caps */
+ , int32_t speed
+ )
+{
+ int32_t new_pitch;
+ int32_t new_stretch;
+ at_limit = false;
+
+ if (pitch_delta < 0)
+ {
+ /* for large jumps, snap up to whole numbers */
+ if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
+ (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
+ {
+ pitch_delta += PITCH_SPEED_PRECISION - ((pitch + pitch_delta) % PITCH_SPEED_PRECISION);
+ }
+
+ new_pitch = pitch + pitch_delta;
+
+ if (new_pitch < PITCH_MIN)
+ {
+ if (!allow_cutoff)
+ {
+ return pitch;
+ }
+ new_pitch = PITCH_MIN;
+ at_limit = true;
+ }
+ }
+ else if (pitch_delta > 0)
+ {
+ /* for large jumps, snap down to whole numbers */
+ if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
+ (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
+ {
+ pitch_delta -= (pitch + pitch_delta) % PITCH_SPEED_PRECISION;
+ }
+
+ new_pitch = pitch + pitch_delta;
+
+ if (new_pitch > PITCH_MAX)
+ {
+ if (!allow_cutoff)
+ return pitch;
+ new_pitch = PITCH_MAX;
+ at_limit = true;
+ }
+ }
+ else
+ {
+ /* pitch_delta == 0 -> no real change */
+ return pitch;
+ }
+ if (rb->dsp_timestretch_available())
+ {
+ /* increase the multiple to increase precision of this calculation */
+ new_stretch = GET_STRETCH(new_pitch, speed);
+ if(new_stretch < STRETCH_MIN)
+ {
+ /* we have to ignore allow_cutoff, because we can't have the */
+ /* stretch go higher than STRETCH_MAX */
+ new_pitch = GET_PITCH(speed, STRETCH_MIN);
+ }
+ else if(new_stretch > STRETCH_MAX)
+ {
+ /* we have to ignore allow_cutoff, because we can't have the */
+ /* stretch go higher than STRETCH_MAX */
+ new_pitch = GET_PITCH(speed, STRETCH_MAX);
+ }
+
+ if(new_stretch >= STRETCH_MAX ||
+ new_stretch <= STRETCH_MIN)
+ {
+ at_limit = true;
+ }
+ }
+
+ rb->sound_set_pitch(new_pitch);
+
+ return new_pitch;
+}
+
+static int32_t get_semitone_from_pitch(int32_t pitch)
+{
+ int semitone = 0;
+ int32_t fractional_index = 0;
+
+ while(semitone < NUM_SEMITONES - 1 &&
+ pitch >= semitone_table[semitone + 1])
+ {
+ semitone++;
+ }
+
+
+ /* now find the fractional part */
+ while(pitch > (cent_interp[fractional_index + 1] *
+ semitone_table[semitone] / PITCH_SPEED_100))
+ {
+ /* Check to make sure fractional_index isn't too big */
+ /* This should never happen. */
+ if(fractional_index >= CENT_INTERP_NUM - 1)
+ {
+ break;
+ }
+ fractional_index++;
+ }
+
+ int32_t semitone_pitch_a = cent_interp[fractional_index] *
+ semitone_table[semitone] /
+ PITCH_SPEED_100;
+ int32_t semitone_pitch_b = cent_interp[fractional_index + 1] *
+ semitone_table[semitone] /
+ PITCH_SPEED_100;
+ /* this will be the integer offset from the cent_interp entry */
+ int32_t semitone_frac_ofs = (pitch - semitone_pitch_a) * CENT_INTERP_INTERVAL /
+ (semitone_pitch_b - semitone_pitch_a);
+ semitone = (semitone + SEMITONE_START) * PITCH_SPEED_PRECISION +
+ fractional_index * CENT_INTERP_INTERVAL +
+ semitone_frac_ofs;
+
+ return semitone;
+}
+
+static int32_t get_pitch_from_semitone(int32_t semitone)
+{
+ int32_t adjusted_semitone = semitone - SEMITONE_START * PITCH_SPEED_PRECISION;
+
+ /* Find the index into the semitone table */
+ int32_t semitone_index = (adjusted_semitone / PITCH_SPEED_PRECISION);
+
+ /* set pitch to the semitone's integer part value */
+ int32_t pitch = semitone_table[semitone_index];
+ /* get the range of the cent modification for future calculation */
+ int32_t pitch_mod_a =
+ cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
+ CENT_INTERP_INTERVAL];
+ int32_t pitch_mod_b =
+ cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
+ CENT_INTERP_INTERVAL + 1];
+ /* figure out the cent mod amount based on the semitone fractional value */
+ int32_t pitch_mod = pitch_mod_a + (pitch_mod_b - pitch_mod_a) *
+ (adjusted_semitone % CENT_INTERP_INTERVAL) / CENT_INTERP_INTERVAL;
+
+ /* modify pitch based on the mod amount we just calculated */
+ return (pitch * pitch_mod + PITCH_SPEED_100 / 2) / PITCH_SPEED_100;
+}
+
+static int32_t pitch_increase_semitone(int32_t pitch,
+ int32_t current_semitone,
+ int32_t semitone_delta
+ , int32_t speed
+ )
+{
+ int32_t new_semitone = current_semitone;
+
+ /* snap to the delta interval */
+ if(current_semitone % semitone_delta != 0)
+ {
+ if(current_semitone > 0 && semitone_delta > 0)
+ new_semitone += semitone_delta;
+ else if(current_semitone < 0 && semitone_delta < 0)
+ new_semitone += semitone_delta;
+
+ new_semitone -= new_semitone % semitone_delta;
+ }
+ else
+ new_semitone += semitone_delta;
+
+ /* clamp the pitch so it doesn't go beyond the pitch limits */
+ if(new_semitone < (SEMITONE_START * PITCH_SPEED_PRECISION))
+ {
+ new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
+ at_limit = true;
+ }
+ else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
+ {
+ new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
+ at_limit = true;
+ }
+
+ int32_t new_pitch = get_pitch_from_semitone(new_semitone);
+
+ int32_t new_stretch = GET_STRETCH(new_pitch, speed);
+
+ /* clamp the pitch so it doesn't go beyond the stretch limits */
+ if( new_stretch > STRETCH_MAX)
+ {
+ new_pitch = GET_PITCH(speed, STRETCH_MAX);
+ new_semitone = get_semitone_from_pitch(new_pitch);
+ at_limit = true;
+ }
+ else if (new_stretch < STRETCH_MIN)
+ {
+ new_pitch = GET_PITCH(speed, STRETCH_MIN);
+ new_semitone = get_semitone_from_pitch(new_pitch);
+ at_limit = true;
+ }
+
+ pitch_increase(pitch, new_pitch - pitch, false
+ , speed
+ );
+
+ return new_semitone;
+}
+
+#ifdef HAVE_TOUCHSCREEN
+/*
+ * Check for touchscreen presses as per sketch above in this file
+ *
+ * goes through each row of the, checks whether the touchscreen
+ * was pressed in it. Then it looks the columns of each row for specific actions
+ */
+static int pitchscreen_do_touchscreen(struct viewport vps[])
+{
+ short x, y;
+ struct viewport *this_vp = &vps[PITCH_TOP];
+ int ret;
+ static bool wait_for_release = false;
+ ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
+
+ /* top row */
+ if (ret > ACTION_UNKNOWN)
+ { /* press on top row, left or right column
+ * only toggle mode if released */
+ int column = this_vp->width / 3;
+ if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
+ return ACTION_PS_TOGGLE_MODE;
+
+
+ else if (x >= column && x <= (2*column))
+ { /* center column pressed */
+ if (ret == BUTTON_REPEAT)
+ return ACTION_PS_INC_BIG;
+ else if (ret & BUTTON_REL)
+ return ACTION_PS_INC_SMALL;
+ }
+ return ACTION_NONE;
+ }
+
+ /* now the center row */
+ this_vp = &vps[PITCH_MID];
+ ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
+
+ if (ret > ACTION_UNKNOWN)
+ {
+ int column = this_vp->width / 3;
+
+ if (x < column)
+ { /* left column */
+ if (ret & BUTTON_REL)
+ {
+ wait_for_release = false;
+ return ACTION_PS_NUDGE_LEFTOFF;
+ }
+ else if (ret & BUTTON_REPEAT)
+ return ACTION_PS_SLOWER;
+ if (!wait_for_release)
+ {
+ wait_for_release = true;
+ return ACTION_PS_NUDGE_LEFT;
+ }
+ }
+ else if (x > (2*column))
+ { /* right column */
+ if (ret & BUTTON_REL)
+ {
+ wait_for_release = false;
+ return ACTION_PS_NUDGE_RIGHTOFF;
+ }
+ else if (ret & BUTTON_REPEAT)
+ return ACTION_PS_FASTER;
+ if (!wait_for_release)
+ {
+ wait_for_release = true;
+ return ACTION_PS_NUDGE_RIGHT;
+ }
+ }
+ else
+ /* center column was pressed */
+ return ACTION_PS_RESET;
+ }
+
+ /* now the bottom row */
+ this_vp = &vps[PITCH_BOTTOM];
+ ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
+
+ if (ret > ACTION_UNKNOWN)
+ {
+ int column = this_vp->width / 3;
+
+ /* left or right column is exit */
+ if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
+ return ACTION_PS_EXIT;
+ else if (x >= column && x <= (2*column))
+ { /* center column was pressed */
+ if (ret & BUTTON_REPEAT)
+ return ACTION_PS_DEC_BIG;
+ else if (ret & BUTTON_REL)
+ return ACTION_PS_DEC_SMALL;
+ }
+ return ACTION_NONE;
+ }
+ return ACTION_NONE;
+}
+
+#endif
+/*
+ returns:
+ 0 on exit
+ 1 if USB was connected
+*/
+
+int gui_syncpitchscreen_run(void)
+{
+ int button;
+ int32_t pitch = rb->sound_get_pitch();
+ int32_t semitone;
+
+ int32_t new_pitch;
+ int32_t pitch_delta;
+ bool nudged = false;
+ int i, updated = 4, decimals = 0;
+ bool exit = false;
+ /* should maybe be passed per parameter later, not needed for now */
+ struct viewport parent[NB_SCREENS];
+ struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
+ int max_lines[NB_SCREENS];
+
+ //push_current_activity(ACTIVITY_PITCHSCREEN);
+
+ int32_t new_speed = 0, new_stretch;
+
+ /* the speed variable holds the apparent speed of the playback */
+ int32_t speed;
+ if (rb->dsp_timestretch_available())
+ {
+ speed = GET_SPEED(pitch, rb->dsp_get_timestretch());
+ }
+ else
+ {
+ speed = pitch;
+ }
+
+
+
+ /* Count decimals for speaking */
+ for (i = PITCH_SPEED_PRECISION; i >= 10; i /= 10)
+ decimals++;
+
+ /* set the semitone index based on the current pitch */
+ semitone = get_semitone_from_pitch(pitch);
+
+ /* initialize pitchscreen vps */
+ FOR_NB_SCREENS(i)
+ {
+ rb->viewport_set_defaults(&parent[i], i);
+ max_lines[i] = viewport_get_nb_lines(&parent[i]);
+ pitchscreen_fix_viewports(&parent[i], pitch_viewports[i]);
+ rb->screens[i]->set_viewport(&parent[i]);
+ rb->screens[i]->clear_viewport();
+
+ /* also, draw the icons now, it's only needed once */
+ pitchscreen_draw_icons(rb->screens[i], &parent[i]);
+ }
+
+
+ while (!exit)
+ {
+ FOR_NB_SCREENS(i)
+ pitchscreen_draw(rb->screens[i], max_lines[i],
+ pitch_viewports[i], pitch, semitone
+ , speed
+ );
+ pitch_delta = 0;
+ new_speed = 0;
+
+ if (rb->global_settings->talk_menu && updated)
+ {
+ rb->talk_shutup();
+ switch (updated)
+ {
+ case 1:
+ if (rb->global_settings->pitch_mode_semitone)
+ rb->talk_value_decimal(semitone, UNIT_SIGNED, decimals, false);
+ else
+ rb->talk_value_decimal(pitch, UNIT_PERCENT, decimals, false);
+ break;
+ case 2:
+ rb->talk_value_decimal(speed, UNIT_PERCENT, decimals, false);
+ break;
+ case 3:
+ speak_pitch_mode(false);
+ break;
+ case 4:
+ if (rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available())
+ rb->talk_id(LANG_PITCH, false);
+ else
+ rb->talk_id(LANG_PLAYBACK_RATE, false);
+ rb->talk_value_decimal(pitch, UNIT_PERCENT, decimals, true);
+ if (rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available())
+ {
+ rb->talk_id(LANG_SPEED, true);
+ rb->talk_value_decimal(speed, UNIT_PERCENT, decimals, true);
+ }
+ speak_pitch_mode(true);
+ break;
+ default:
+ break;
+ }
+ }
+ updated = 0;
+
+ button = rb->get_action(CONTEXT_PITCHSCREEN, HZ);
+
+#ifdef HAVE_TOUCHSCREEN
+ if (button == ACTION_TOUCHSCREEN)
+ {
+ FOR_NB_SCREENS(i)
+ button = pitchscreen_do_touchscreen(pitch_viewports[i]);
+ }
+#endif
+ switch (button)
+ {
+ case ACTION_PS_INC_SMALL:
+ if(rb->global_settings->pitch_mode_semitone)
+ pitch_delta = SEMITONE_SMALL_DELTA;
+ else
+ pitch_delta = PITCH_SMALL_DELTA;
+ updated = 1;
+ break;
+
+ case ACTION_PS_INC_BIG:
+ if(rb->global_settings->pitch_mode_semitone)
+ pitch_delta = SEMITONE_BIG_DELTA;
+ else
+ pitch_delta = PITCH_BIG_DELTA;
+ updated = 1;
+ break;
+
+ case ACTION_PS_DEC_SMALL:
+ if(rb->global_settings->pitch_mode_semitone)
+ pitch_delta = -SEMITONE_SMALL_DELTA;
+ else
+ pitch_delta = -PITCH_SMALL_DELTA;
+ updated = 1;
+ break;
+
+ case ACTION_PS_DEC_BIG:
+ if(rb->global_settings->pitch_mode_semitone)
+ pitch_delta = -SEMITONE_BIG_DELTA;
+ else
+ pitch_delta = -PITCH_BIG_DELTA;
+ updated = 1;
+ break;
+
+ case ACTION_PS_NUDGE_RIGHT:
+ if (!rb->global_settings->pitch_mode_timestretch)
+ {
+ new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
+ , speed
+ );
+ nudged = (new_pitch != pitch);
+ pitch = new_pitch;
+ semitone = get_semitone_from_pitch(pitch);
+ speed = pitch;
+ updated = nudged ? 1 : 0;
+ break;
+ }
+ else
+ {
+ new_speed = speed + SPEED_SMALL_DELTA;
+ at_limit = false;
+ updated = 2;
+ }
+ break;
+
+ case ACTION_PS_FASTER:
+ if (rb->global_settings->pitch_mode_timestretch)
+ {
+ new_speed = speed + SPEED_BIG_DELTA;
+ /* snap to whole numbers */
+ if(new_speed % PITCH_SPEED_PRECISION != 0)
+ new_speed -= new_speed % PITCH_SPEED_PRECISION;
+ at_limit = false;
+ updated = 2;
+ }
+ break;
+
+ case ACTION_PS_NUDGE_RIGHTOFF:
+ if (nudged)
+ {
+ pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
+ , speed
+ );
+ speed = pitch;
+ semitone = get_semitone_from_pitch(pitch);
+ nudged = false;
+ updated = 1;
+ }
+ break;
+
+ case ACTION_PS_NUDGE_LEFT:
+ if (!rb->global_settings->pitch_mode_timestretch)
+ {
+ new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
+ , speed
+ );
+ nudged = (new_pitch != pitch);
+ pitch = new_pitch;
+ semitone = get_semitone_from_pitch(pitch);
+ speed = pitch;
+ updated = nudged ? 1 : 0;
+ break;
+ }
+ else
+ {
+ new_speed = speed - SPEED_SMALL_DELTA;
+ at_limit = false;
+ updated = 2;
+ }
+ break;
+
+ case ACTION_PS_SLOWER:
+ if (rb->global_settings->pitch_mode_timestretch)
+ {
+ new_speed = speed - SPEED_BIG_DELTA;
+ /* snap to whole numbers */
+ if(new_speed % PITCH_SPEED_PRECISION != 0)
+ new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
+ at_limit = false;
+ updated = 2;
+ }
+ break;
+
+ case ACTION_PS_NUDGE_LEFTOFF:
+ if (nudged)
+ {
+ pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
+ , speed
+ );
+ speed = pitch;
+ semitone = get_semitone_from_pitch(pitch);
+ nudged = false;
+ updated = 1;
+ }
+ break;
+
+ case ACTION_PS_RESET:
+ pitch = PITCH_SPEED_100;
+ rb->sound_set_pitch(pitch);
+ speed = PITCH_SPEED_100;
+ if (rb->dsp_timestretch_available())
+ {
+ rb->dsp_set_timestretch(PITCH_SPEED_100);
+ at_limit = false;
+ }
+ semitone = get_semitone_from_pitch(pitch);
+ updated = 4;
+ break;
+
+ case ACTION_PS_TOGGLE_MODE:
+ rb->global_settings->pitch_mode_semitone = !rb->global_settings->pitch_mode_semitone;
+
+ if (rb->dsp_timestretch_available() && !rb->global_settings->pitch_mode_semitone)
+ {
+ rb->global_settings->pitch_mode_timestretch = !rb->global_settings->pitch_mode_timestretch;
+ if(!rb->global_settings->pitch_mode_timestretch)
+ {
+ /* no longer in timestretch mode. Reset speed */
+ speed = pitch;
+ rb->dsp_set_timestretch(PITCH_SPEED_100);
+ }
+ }
+ rb->settings_save();
+ updated = 3;
+ break;
+
+ case ACTION_PS_EXIT:
+ exit = true;
+ break;
+
+ default:
+ if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
+ return 1;
+ break;
+ }
+ if (pitch_delta)
+ {
+ if (rb->global_settings->pitch_mode_semitone)
+ {
+ semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
+ , speed
+ );
+ pitch = get_pitch_from_semitone(semitone);
+ }
+ else
+ {
+ pitch = pitch_increase(pitch, pitch_delta, true
+ , speed
+ );
+ semitone = get_semitone_from_pitch(pitch);
+ }
+ if (rb->global_settings->pitch_mode_timestretch)
+ {
+ /* do this to make sure we properly obey the stretch limits */
+ new_speed = speed;
+ }
+ else
+ {
+ speed = pitch;
+ }
+ }
+
+ if(new_speed)
+ {
+ new_stretch = GET_STRETCH(pitch, new_speed);
+
+ /* limit the amount of stretch */
+ if(new_stretch > STRETCH_MAX)
+ {
+ new_stretch = STRETCH_MAX;
+ new_speed = GET_SPEED(pitch, new_stretch);
+ }
+ else if(new_stretch < STRETCH_MIN)
+ {
+ new_stretch = STRETCH_MIN;
+ new_speed = GET_SPEED(pitch, new_stretch);
+ }
+
+ new_stretch = GET_STRETCH(pitch, new_speed);
+ if(new_stretch >= STRETCH_MAX ||
+ new_stretch <= STRETCH_MIN)
+ {
+ at_limit = true;
+ }
+
+ /* set the amount of stretch */
+ rb->dsp_set_timestretch(new_stretch);
+
+ /* update the speed variable with the new speed */
+ speed = new_speed;
+
+ /* Reset new_speed so we only call dsp_set_timestretch */
+ /* when needed */
+ new_speed = 0;
+ }
+ }
+
+ //rb->pcmbuf_set_low_latency(false);
+ //pop_current_activity();
+
+ /* Clean up */
+ FOR_NB_SCREENS(i)
+ {
+ rb->screens[i]->set_viewport(NULL);
+ }
+
+ return 0;
+}
+
+static int arg_callback(char argchar, const char **parameter)
+{
+ int ret;
+ long num, dec;
+ bool bret;
+ //rb->splashf(100, "Arg: %c", argchar);
+ while (*parameter[0] > '/' && ispunct(*parameter[0])) (*parameter)++;
+ switch (tolower(argchar))
+ {
+ case 'q' :
+ pitch_vars.flags &= ~PVAR_VERBOSE;
+ break;
+ case 'g' :
+ pitch_vars.flags |= PVAR_GUI;
+ break;
+ case 'p' :
+ ret = longnum_parse(parameter, &num, &dec);
+ if (ret)
+ {
+ dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION);
+ if (num < 0)
+ dec = -dec;
+ pitch_vars.pitch = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION));
+ }
+ break;
+ case 'k' :
+ ret = bool_parse(parameter, &bret);
+ if (ret)
+ {
+ if(!bret && rb->dsp_timestretch_available())
+ {
+ /* no longer in timestretch mode. Reset speed */
+ rb->dsp_set_timestretch(PITCH_SPEED_100);
+ }
+ rb->dsp_timestretch_enable(bret);
+ if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
+ rb->splashf(HZ, "Timestretch: %s", bret ? "true" : "false");
+ int n = 10; /* 1 second */
+ while (bret && n-- > 0 && !rb->dsp_timestretch_available())
+ {
+ rb->sleep(HZ / 10);
+ }
+ }
+ break;
+ case 's' :
+ ret = longnum_parse(parameter, &num, &dec);
+ if (ret && rb->dsp_timestretch_available())
+ {
+ dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION);
+ if (num < 0)
+ break;
+ pitch_vars.speed = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION));
+
+ }
+ break;
+ default :
+ rb->splashf(HZ, "Unknown switch '%c'",argchar);
+ //return 0;
+ }
+
+ return 1;
+}
+
+void fill_pitchvars(struct pvars *pv)
+{
+ if (!pv)
+ return;
+ pv->pitch = rb->sound_get_pitch();
+
+ /* the speed variable holds the apparent speed of the playback */
+ if (rb->dsp_timestretch_available())
+ {
+ pv->speed = GET_SPEED(pv->pitch, rb->dsp_get_timestretch());
+ }
+ else
+ {
+ pv->speed = pv->pitch;
+ }
+
+ pv->stretch = GET_STRETCH(pv->pitch, pv->speed);
+ pv->flags |= PVAR_VERBOSE;
+
+}
+/* plugin entry point */
+enum plugin_status plugin_start(const void* parameter)
+{
+ /* pitch_screen
+ * accepts args -q, -g, -p=, -s=, -k=; (= sign is optional)
+ * -q silences output splash
+ * -g runs the gui (DEFAULT)
+ * -p100 would set pitch to 100%
+ * -s=90 sets speed to 90% if timestrech is enabled
+ * -k=true -k1 enables time stretch -k0 -kf-kn disables
+*/
+ bool gui = false;
+ rb->pcmbuf_set_low_latency(true);
+
+ /* Figure out whether to be in timestretch mode */
+ if (parameter == NULL) /* gui mode */
+ {
+ if (rb->global_settings->pitch_mode_timestretch && !rb->dsp_timestretch_available())
+ {
+ rb->global_settings->pitch_mode_timestretch = false;
+ rb->settings_save();
+ }
+ gui = true;
+ }
+ else
+ {
+ struct pvars cur;
+ fill_pitchvars(&cur);
+ fill_pitchvars(&pitch_vars);
+ argparse((const char*) parameter, -1, &arg_callback);
+ if (pitch_vars.pitch != cur.pitch)
+ {
+ rb->sound_set_pitch(pitch_vars.pitch);
+ pitch_vars.pitch = rb->sound_get_pitch();
+ if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
+ rb->splashf(HZ, "pitch: %ld.%02ld%%",
+ pitch_vars.pitch / PITCH_SPEED_PRECISION,
+ pitch_vars.pitch % PITCH_SPEED_PRECISION);
+ }
+ if (pitch_vars.speed != cur.speed)
+ {
+ pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed);
+
+ /* limit the amount of stretch */
+ if(pitch_vars.stretch > STRETCH_MAX)
+ {
+ pitch_vars.stretch = STRETCH_MAX;
+ pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch);
+ }
+ else if(pitch_vars.stretch < STRETCH_MIN)
+ {
+ pitch_vars.stretch = STRETCH_MIN;
+ pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch);
+ }
+
+ pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed);
+ if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
+ rb->splashf(HZ, "speed: %ld.%02ld%%",
+ pitch_vars.speed / PITCH_SPEED_PRECISION,
+ pitch_vars.speed % PITCH_SPEED_PRECISION);
+ /* set the amount of stretch */
+ rb->dsp_set_timestretch(pitch_vars.stretch);
+ }
+ gui = ((pitch_vars.flags & PVAR_GUI) == PVAR_GUI);
+ if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
+ rb->splashf(HZ, "GUI: %d", gui);
+
+ }
+
+ if (gui && gui_syncpitchscreen_run() == 1)
+ return PLUGIN_USB_CONNECTED;
+ rb->pcmbuf_set_low_latency(false);
+ return PLUGIN_OK;
+}
diff --git a/apps/plugins/plugins.make b/apps/plugins/plugins.make
index ac94a04c59..b430bd2792 100644
--- a/apps/plugins/plugins.make
+++ b/apps/plugins/plugins.make
@@ -101,8 +101,35 @@ else
PLUGINLIBFLAGS = $(PLUGINFLAGS) -ffunction-sections -fdata-sections
endif
+ROOT_PLUGINSLIB_DIR := $(ROOTDIR)/apps/plugins/lib
+BUILD_PLUGINSLIB_DIR := $(BUILDDIR)/apps/plugins/lib
+
+# action_helper #
+ACTION_REQ := $(addprefix $(ROOT_PLUGINSLIB_DIR)/,action_helper.pl action_helper.h) \
+ $(BUILD_PLUGINSLIB_DIR)/pluginlib_actions.o
+
+# special rule for generating and compiling action_helper
+$(BUILD_PLUGINSLIB_DIR)/action_helper.o: $(ACTION_REQ)
+ $(SILENT)mkdir -p $(dir $@)
+ $(call PRINTS,GEN $(@F))$(CC) $(PLUGINFLAGS) $(INCLUDES) -E -P \
+ $(ROOT_PLUGINSLIB_DIR)/pluginlib_actions.h - < /dev/null | $< > $(basename $@).c
+ $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(ROOT_PLUGINSLIB_DIR) \
+ $(PLUGINLIBFLAGS) -c $(basename $@).c -o $@
+
+# button_helper #
+BUTTON_REQ := $(addprefix $(ROOT_PLUGINSLIB_DIR)/,button_helper.pl button_helper.h) \
+ $(BUILD_PLUGINSLIB_DIR)/action_helper.o
+
+# special rule for generating and compiling button_helper
+$(BUILD_PLUGINSLIB_DIR)/button_helper.o: $(BUTTON_REQ) $(ROOTDIR)/firmware/export/button.h
+ $(SILENT)mkdir -p $(dir $@)
+ $(call PRINTS,GEN $(@F))$(CC) $(PLUGINFLAGS) $(INCLUDES) -dM -E -P \
+ $(addprefix -include ,button-target.h button.h) - < /dev/null | $< > $(basename $@).c
+ $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(ROOT_PLUGINSLIB_DIR) \
+ $(PLUGINLIBFLAGS) -c $(basename $@).c -o $@
+
# special pattern rule for compiling plugin lib (with function and data sections)
-$(BUILDDIR)/apps/plugins/lib/%.o: $(ROOTDIR)/apps/plugins/lib/%.c
+$(BUILD_PLUGINSLIB_DIR)/%.o: $(ROOT_PLUGINSLIB_DIR)/%.c
$(SILENT)mkdir -p $(dir $@)
$(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(PLUGINLIBFLAGS) -c $< -o $@
diff --git a/apps/plugins/random_folder_advance_config.c b/apps/plugins/random_folder_advance_config.c
index ca0d5f397d..2c9fb411ac 100644
--- a/apps/plugins/random_folder_advance_config.c
+++ b/apps/plugins/random_folder_advance_config.c
@@ -211,7 +211,7 @@ static bool custom_dir(void)
rb->close(fd2);
if(errors)
/* Press button to continue */
- rb->get_action(CONTEXT_STD, TIMEOUT_BLOCK);
+ rb->get_action(CONTEXT_STD, TIMEOUT_BLOCK);
}
else
return false;
@@ -259,11 +259,11 @@ static int load_list(void)
{
return -2;
}
-
+
rb->read(myfd,buffer,buffer_size);
rb->close(myfd);
list = (struct file_format *)buffer;
-
+
return 0;
}
@@ -288,7 +288,7 @@ static int save_list(void)
rb->lseek(myfd,0,SEEK_SET);
rb->write(myfd,&dirs_count,sizeof(int));
rb->close(myfd);
-
+
return 1;
}
@@ -298,22 +298,22 @@ static int edit_list(void)
bool exit = false;
int button,i;
int selection, ret = 0;
-
+
/* load the dat file if not already done */
if ((list == NULL || list->count == 0) && (i = load_list()) != 0)
{
rb->splashf(HZ*2, "Could not load %s, rv = %d", RFA_FILE, i);
return -1;
}
-
+
dirs_count = list->count;
-
+
rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL);
rb->gui_synclist_set_icon_callback(&lists,NULL);
rb->gui_synclist_set_nb_items(&lists,list->count);
rb->gui_synclist_limit_scroll(&lists,true);
rb->gui_synclist_select_item(&lists, 0);
-
+
while (!exit)
{
rb->gui_synclist_draw(&lists);
@@ -387,22 +387,22 @@ static int export_list_to_file_text(void)
rb->splashf(HZ*2, "Could not load %s, rv = %d", RFA_FILE, i);
return 0;
}
-
+
if (list->count <= 0)
{
rb->splashf(HZ*2, "no dirs in list file: %s", RFA_FILE);
return 0;
}
-
+
/* create and open the file */
int myfd = rb->creat(RFA_FILE_TEXT, 0666);
if (myfd < 0)
{
- rb->splashf(HZ*4, "failed to open: fd = %d, file = %s",
+ rb->splashf(HZ*4, "failed to open: fd = %d, file = %s",
myfd, RFA_FILE_TEXT);
return -1;
}
-
+
/* write each directory to file */
for (i = 0; i < list->count; i++)
{
@@ -411,7 +411,7 @@ static int export_list_to_file_text(void)
rb->fdprintf(myfd, "%s\n", list->folder[i]);
}
}
-
+
rb->close(myfd);
rb->splash(HZ, "Done");
return 1;
@@ -420,7 +420,7 @@ static int export_list_to_file_text(void)
static int import_list_from_file_text(void)
{
char line[MAX_PATH];
-
+
buffer = rb->plugin_get_audio_buffer(&buffer_size);
if (buffer == NULL)
{
@@ -434,11 +434,11 @@ static int import_list_from_file_text(void)
rb->splashf(HZ*2, "failed to open: %s", RFA_FILE_TEXT);
return -1;
}
-
+
/* set the list structure, and initialize count */
list = (struct file_format *)buffer;
list->count = 0;
-
+
while ((rb->read_line(myfd, line, MAX_PATH - 1)) > 0)
{
/* copy the dir name, and skip the newline */
@@ -448,16 +448,16 @@ static int import_list_from_file_text(void)
{
if (line[len-1] == 0x0A || line[len-1] == 0x0D)
line[len-1] = 0x00;
- if (len > 1 &&
+ if (len > 1 &&
(line[len-2] == 0x0A || line[len-2] == 0x0D))
line[len-2] = 0x00;
}
-
+
rb->strcpy(list->folder[list->count++], line);
}
-
+
rb->close(myfd);
-
+
if (list->count == 0)
{
load_list();
@@ -484,14 +484,14 @@ static int start_shuffled_play(void)
rb->splashf(HZ*2, "Not enough memory for shuffling");
return 0;
}
-
+
/* load the dat file if not already done */
if ((list == NULL || list->count == 0) && (i = load_list()) != 0)
{
rb->splashf(HZ*2, "Could not load %s, rv = %d", RFA_FILE, i);
return 0;
}
-
+
if (list->count <= 0)
{
rb->splashf(HZ*2, "no dirs in list file: %s", RFA_FILE);
@@ -507,7 +507,7 @@ static int start_shuffled_play(void)
}
for(i=0;i<list->count;i++)
order[i]=i;
-
+
for(i = list->count - 1; i >= 0; i--)
{
/* the rand is from 0 to RAND_MAX, so adjust to our value range */
@@ -518,7 +518,7 @@ static int start_shuffled_play(void)
order[candidate] = order[i];
order[i] = store;
}
-
+
/* We don't want whatever is playing */
if (!(rb->playlist_remove_all_tracks(NULL) == 0
&& rb->playlist_create(NULL, NULL) == 0))
@@ -643,6 +643,6 @@ enum plugin_status plugin_start(const void* parameter)
#endif
cancel = false;
-
+
return main_menu();
}
diff --git a/apps/plugins/rb_info.c b/apps/plugins/rb_info.c
new file mode 100644
index 0000000000..f82c80c0cf
--- /dev/null
+++ b/apps/plugins/rb_info.c
@@ -0,0 +1,494 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// __ \_/ ___\| |/ /| __ \ / __ \ \/ /
+ * Jukebox | | ( (__) ) \___| ( | \_\ ( (__) ) (
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2020 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.
+ *
+ ****************************************************************************/
+
+/* WIP rb_info common info that you wonder about when rockboxing?
+ */
+
+#include "plugin.h"
+#include "lang_enum.h"
+#include "../open_plugin.h"
+#include "logf.h"
+#include "lib/action_helper.h"
+#include "lib/button_helper.h"
+#include "lib/pluginlib_actions.h"
+
+#define MENU_ID(x) (((void*)&"RPBUTACNGX\0" + x))
+enum {
+ M_ROOT = 0,
+ M_PATHS,
+ M_BUFFERS,
+ M_BUTTONS,
+ M_BTNTEST,
+ M_ACTIONS,
+ M_CONTEXTS,
+ M_ACTTEST,
+ M_PLUGINS,
+ M_EXIT,
+ M_LAST_ITEM //ITEM COUNT
+};
+
+#define MENU_ID_PLUGINS_ITEMS 5
+
+/*Action test and Button test*/
+static struct menu_test_t {
+ int count;
+ int context;
+ int last_btn_or_act;
+} m_test;
+
+struct menu_buffer_t { const char *name; size_t size;};
+static const struct menu_buffer_t m_buffer[] =
+{
+#ifndef MAX_LOGF_SIZE
+#define MAX_LOGF_SIZE (0)
+#endif
+#ifndef CACHE_SIZE
+#define CACHE_SIZE (0)
+#endif
+ {"thread stack", DEFAULT_STACK_SIZE},
+ {"plugin buffer", PLUGIN_BUFFER_SIZE},
+ {"frame_buffer", FRAMEBUFFER_SIZE},
+ {"codec_buffer", CODEC_SIZE},
+ {"logf_buffer", MAX_LOGF_SIZE},
+ {"cache", CACHE_SIZE},
+};
+
+/* stringify the macro value */
+#define MACROVAL(x) MACROSTR(x)
+#define MACROSTR(x) #x
+static int main_last_sel = 0;
+static struct gui_synclist lists;
+static void synclist_set(char*, int, int, int);
+
+struct paths { const char *name; const char *path; };
+static const struct paths paths[] = {
+ {"Home", ""HOME_DIR},
+ {"Rockbox", ""ROCKBOX_DIR},
+ {"Plugins", ""PLUGIN_DIR},
+ {"Codecs", ""CODECS_DIR},
+ {"WPS", ""WPS_DIR},
+ {"SBS", ""SBS_DIR},
+ {"Theme", ""THEME_DIR},
+ {"Font", ""FONT_DIR},
+ {"Icon", ""ICON_DIR},
+ {"Backdrop", ""BACKDROP_DIR},
+ {"Eq", ""EQS_DIR},
+ {"Rec Presets", ""RECPRESETS_DIR},
+ {"Recordings", ""REC_BASE_DIR,},
+ {"Fm Presets", ""FMPRESET_PATH},
+ {"MAX_PATH", ""MACROVAL(MAX_PATH)" bytes"},
+};
+
+struct mainmenu { const char *name; void *menuid; int items;};
+static struct mainmenu mainmenu[M_LAST_ITEM] = {
+#define MENU_ITEM(ID, NAME, COUNT) [ID]{NAME, MENU_ID(ID), (int)COUNT}
+MENU_ITEM(M_ROOT, "Rockbox Info Plugin", M_LAST_ITEM),
+MENU_ITEM(M_PATHS, ID2P(LANG_SHOW_PATH), ARRAYLEN(paths)),
+MENU_ITEM(M_BUFFERS, ID2P(LANG_BUFFER_STAT), ARRAYLEN(m_buffer)),
+MENU_ITEM(M_BUTTONS, "Buttons", -1), /* Set at runtime in plugin_start: */
+MENU_ITEM(M_BTNTEST, "Button test", 2),
+MENU_ITEM(M_ACTIONS, "Actions", LAST_ACTION_PLACEHOLDER),
+MENU_ITEM(M_CONTEXTS, "Contexts", LAST_CONTEXT_PLACEHOLDER ),
+MENU_ITEM(M_ACTTEST, "Action test", 3),
+MENU_ITEM(M_PLUGINS, ID2P(LANG_PLUGINS), MENU_ID_PLUGINS_ITEMS),
+MENU_ITEM(M_EXIT, ID2P(LANG_MENU_QUIT), 0),
+#undef MENU_ITEM
+};
+
+static const struct mainmenu *mainitem(int selected_item)
+{
+ static const struct mainmenu empty = {0};
+ if (selected_item >= 0 && selected_item < (int) ARRAYLEN(mainmenu))
+ return &mainmenu[selected_item];
+ else
+ return &empty;
+}
+
+static void cleanup(void *parameter)
+{
+ (void)parameter;
+
+}
+
+static const char *menu_plugin_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ (void)data;
+ buf[0] = '\0';
+ switch(selected_item)
+ {
+ case 0:
+ rb->snprintf(buf, buf_len, "%s: [%d bytes] ", "plugin_api", (int)sizeof(struct plugin_api));
+ break;
+ case 1:
+ rb->snprintf(buf, buf_len, "%s: [%d bytes] ", "plugin buffer", PLUGIN_BUFFER_SIZE);
+ break;
+ case 2:
+ rb->snprintf(buf, buf_len, "%s: [%d bytes] ", "frame_buffer", (int)FRAMEBUFFER_SIZE);
+ break;
+ case 3:
+ rb->snprintf(buf, buf_len, "%s: [W: %d H:%d] ", "LCD", LCD_WIDTH, LCD_HEIGHT);
+ break;
+ case 4:
+ rb->snprintf(buf, buf_len, "%s: [%d bits] ", "fb_data", (int)(sizeof(fb_data) * CHAR_BIT));
+ break;
+ case 5:
+ break;
+ }
+ return buf;
+}
+
+static const char *menu_button_test_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ (void)data;
+ int curbtn = BUTTON_NONE;
+ buf[0] = '\0';
+ switch(selected_item)
+ {
+ case 0:
+ rb->snprintf(buf, buf_len, "%s: [%s] ", "Button test",
+ m_test.count > 0 ? "true":"false");
+ break;
+ case 1:
+ if (m_test.count > 0)
+ {
+ if (m_test.count <= 2)
+ curbtn = rb->button_get_w_tmo(HZ * 2);
+ else
+ m_test.last_btn_or_act = BUTTON_NONE;
+ if (curbtn == BUTTON_NONE)
+ {
+ m_test.count--;
+ }
+ else
+ m_test.last_btn_or_act = curbtn;
+ }
+ get_button_names(buf, buf_len, m_test.last_btn_or_act);
+
+ break;
+ }
+ return buf;
+}
+
+static const char *menu_action_test_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ (void)data;
+ const char *fmtstr;
+ int curact = ACTION_NONE;
+ buf[0] = '\0';
+ switch(selected_item)
+ {
+ case 0:
+ rb->snprintf(buf, buf_len, "%s: [%s] ", "Action test",
+ m_test.count > 0 ? "true":"false");
+ break;
+ case 1:
+ if (m_test.count <= 0)
+ {
+ if (m_test.context <= 0)
+ fmtstr = "%s > ";
+ else if (m_test.context >= LAST_CONTEXT_PLACEHOLDER - 1)
+ fmtstr = "< %s ";
+ else
+ fmtstr = "< %s > ";
+ }
+ else
+ fmtstr = "%s";
+
+ rb->snprintf(buf, buf_len, fmtstr, context_name(m_test.context));
+ break;
+ case 2:
+ if (m_test.count > 0)
+ {
+ if (m_test.count <= 2)
+ curact = rb->get_action(m_test.context, HZ * 2);
+ else
+ m_test.last_btn_or_act = ACTION_NONE;
+ if (curact == ACTION_NONE && rb->button_get(false) == BUTTON_NONE)
+ {
+ m_test.count--;
+ }
+ else
+ {
+ m_test.last_btn_or_act = curact;
+ m_test.count = 2;
+ }
+ }
+ return action_name(m_test.last_btn_or_act);
+
+ break;
+ }
+ return buf;
+}
+
+static const char* list_get_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ buf[0] = '\0';
+ if (data == MENU_ID(M_ROOT))
+ return mainitem(selected_item)->name;
+ else if (selected_item == 0) /*header text*/
+ return mainitem(main_last_sel)->name;
+ else if (selected_item >= mainitem(main_last_sel)->items - 1)
+ return ID2P(LANG_BACK);
+
+ if (data == MENU_ID(M_PATHS))
+ {
+ selected_item--;
+ if (selected_item >= 0 && selected_item < mainitem(M_PATHS)->items)
+ {
+ const struct paths *cur = &paths[selected_item];
+ rb->snprintf(buf, buf_len, "%s: [%s] ", cur->name, cur->path);
+ return buf;
+ }
+ }
+ else if (data == MENU_ID(M_BUTTONS))
+ {
+ const struct available_button *btn = &available_buttons[selected_item - 1];
+ rb->snprintf(buf, buf_len, "%s: [0x%X] ", btn->name, (unsigned int) btn->value);
+ return buf;
+ }
+ else if (data == MENU_ID(M_BTNTEST))
+ return menu_button_test_name_cb(selected_item - 1, data, buf, buf_len);
+ else if (data == MENU_ID(M_ACTIONS))
+ return action_name(selected_item - 1);
+ else if (data == MENU_ID(M_CONTEXTS))
+ return context_name(selected_item - 1);
+ else if (data == MENU_ID(M_ACTTEST))
+ return menu_action_test_name_cb(selected_item - 1, data, buf, buf_len);
+ else if (data == MENU_ID(M_BUFFERS))
+ {
+ const struct menu_buffer_t *bufm = &m_buffer[selected_item - 1];
+ rb->snprintf(buf, buf_len, "%s: [%ld bytes] ", bufm->name, (long)bufm->size);
+ return buf;
+ }
+ else if (data == MENU_ID(M_PLUGINS))
+ {
+ return menu_plugin_name_cb(selected_item - 1, data, buf, buf_len);
+ }
+ return buf;
+}
+
+static int list_voice_cb(int list_index, void* data)
+{
+ if (!rb->global_settings->talk_menu)
+ return -1;
+
+ if (data == MENU_ID(M_ROOT))
+ {
+ const char * name = mainitem(list_index)->name;
+ long id = P2ID((const unsigned char *)name);
+ if(id>=0)
+ rb->talk_id(id, true);
+ else
+ rb->talk_spell(name, true);
+ }
+ else if (data == MENU_ID(M_BUFFERS) || data == MENU_ID(M_PLUGINS))
+ {
+ char buf[64];
+ const char* name = list_get_name_cb(list_index, data, buf, sizeof(buf));
+ long id = P2ID((const unsigned char *)name);
+ if(id>=0)
+ rb->talk_id(id, true);
+ else
+ {
+ char* bytstr = rb->strcasestr(name, "bytes");
+ if (bytstr != NULL)
+ *bytstr = '\0';
+ rb->talk_spell(name, true);
+ }
+ }
+ else
+ {
+ char buf[64];
+ const char* name = list_get_name_cb(list_index, data, buf, sizeof(buf));
+ long id = P2ID((const unsigned char *)name);
+ if(id>=0)
+ rb->talk_id(id, true);
+ else
+ rb->talk_spell(name, true);
+ }
+ return 0;
+}
+
+int menu_action_cb(int action, int selected_item, bool* exit, struct gui_synclist *lists)
+{
+ if (lists->data == MENU_ID(M_ACTTEST))
+ {
+ if (selected_item == 2) /* context */
+ {
+ int ctx = m_test.context;
+ if (action == ACTION_STD_OK)
+ m_test.context++;
+ else if (action == ACTION_STD_CANCEL)
+ m_test.context--;
+
+ if (m_test.context < 0)
+ m_test.context = 0;
+ else if (m_test.context >= LAST_CONTEXT_PLACEHOLDER)
+ m_test.context = LAST_CONTEXT_PLACEHOLDER - 1;
+
+ if (ctx != m_test.context)
+ rb->gui_synclist_speak_item(lists);
+
+ goto default_handler;
+ }
+ if (action == ACTION_STD_OK)
+ {
+ if (selected_item == 1 || selected_item == 3)
+ {
+ m_test.count = 3;
+ rb->gui_synclist_select_item(lists, 3);
+ }
+ }
+ }
+ else if (lists->data == MENU_ID(M_BTNTEST))
+ {
+ if (action == ACTION_STD_OK)
+ {
+ if (selected_item == 1 || selected_item == 2)
+ {
+ m_test.count = 3;
+ rb->gui_synclist_select_item(lists, 2);
+ }
+ }
+ }
+ if (action == ACTION_STD_OK)
+ {
+ if (lists->data == MENU_ID(M_ROOT))
+ {
+ rb->memset(&m_test, 0, sizeof(struct menu_test_t));
+ const struct mainmenu *cur = mainitem(selected_item);
+ if (cur->menuid == NULL || cur->menuid == MENU_ID(M_EXIT))
+ *exit = true;
+ else
+ {
+ main_last_sel = selected_item;
+ synclist_set(cur->menuid, 1, cur->items, 1);
+ rb->gui_synclist_draw(lists);
+ }
+ }
+ else if (selected_item <= 0) /* title */
+ {
+ rb->gui_synclist_select_item(lists, 1);
+ }
+ else if (selected_item >= (mainitem(main_last_sel)->items) - 1)/*back*/
+ {
+ action = ACTION_STD_CANCEL;
+ }
+ else if (lists->data == MENU_ID(M_ACTIONS) ||
+ lists->data == MENU_ID(M_CONTEXTS))
+ {
+ char buf[MAX_PATH];
+ const char *name = list_get_name_cb(selected_item, lists->data, buf, sizeof(buf));
+ /* splash long enough to get fingers off button then wait for new button press */
+ rb->splashf(HZ / 2, "%s %d (0x%X)", name, selected_item -1, selected_item -1);
+ rb->button_get(true);
+ }
+ }
+ if (action == ACTION_STD_CANCEL)
+ {
+ if (lists->data != MENU_ID(M_ROOT))
+ {
+ const struct mainmenu *mainm = &mainmenu[0];
+ synclist_set(mainm->menuid, main_last_sel, mainm->items, 1);
+ rb->gui_synclist_draw(lists);
+ }
+ else
+ *exit = true;
+ }
+default_handler:
+ if (rb->default_event_handler_ex(action, cleanup, NULL) == SYS_USB_CONNECTED)
+ {
+ *exit = true;
+ return PLUGIN_USB_CONNECTED;
+ }
+ return PLUGIN_OK;
+}
+
+static void synclist_set(char* menu_id, int selected_item, int items, int sel_size)
+{
+ if (items <= 0)
+ return;
+ if (selected_item < 0)
+ selected_item = 0;
+
+ list_voice_cb(0, menu_id);
+ rb->gui_synclist_init(&lists,list_get_name_cb,
+ menu_id, false, sel_size, NULL);
+
+ rb->gui_synclist_set_icon_callback(&lists,NULL);
+ rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
+ rb->gui_synclist_set_nb_items(&lists,items);
+ rb->gui_synclist_limit_scroll(&lists,true);
+ rb->gui_synclist_select_item(&lists, selected_item);
+
+}
+
+enum plugin_status plugin_start(const void* parameter)
+{
+ int ret = PLUGIN_OK;
+ int selected_item = -1;
+ int action;
+ bool redraw = true;
+ bool exit = false;
+ if (parameter)
+ {
+ //
+ }
+ mainmenu[M_BUTTONS].items = available_button_count;
+ /* add header and back item to each submenu */
+ for (int i = 1; i < M_LAST_ITEM; i++)
+ mainmenu[i].items += 2;
+
+ if (!exit)
+ {
+ const struct mainmenu *mainm = &mainmenu[0];
+ synclist_set(mainm->menuid, main_last_sel, mainm->items, 1);
+ rb->gui_synclist_draw(&lists);
+
+ while (!exit)
+ {
+ action = rb->get_action(CONTEXT_LIST, HZ / 10);
+ if (m_test.count > 0)
+ action = ACTION_REDRAW;
+
+ if (action == ACTION_NONE)
+ {
+ if (redraw)
+ {
+ action = ACTION_REDRAW;
+ redraw = false;
+ }
+ }
+ else
+ redraw = true;
+ if (rb->gui_synclist_do_button(&lists,&action,LIST_WRAP_UNLESS_HELD))
+ continue;
+ selected_item = rb->gui_synclist_get_sel_pos(&lists);
+ ret = menu_action_cb(action, selected_item, &exit, &lists);
+ }
+ }
+
+ return ret;
+}