3237ae4a4f
I'm currently running up against the limitations of the lcd_draw functions I want these functions to be able to be used on any size buffer not just buffers with a stride matching the underlying device [DONE] allow the framebuffer to be decoupled from the device framebuffer [DONE need examples] allow for some simple blit like transformations [DONE] remove the device framebuffer from the plugin api [DONE}ditto remote framebuffer [DONE] remove _viewport_get_framebuffer you can call struct *vp = lcd_set_viewport(NULL) and vp->buffer->fb_ptr while remote lcds may compile (and work in the sim) its not been tested on targets [FIXED] backdrops need work to be screen agnostic [FIXED] screen statusbar is not being combined into the main viewport correctly yet [FIXED] screen elements are displayed incorrectly after switch to void* [FIXED] core didn't restore proper viewport on splash etc. [NEEDS TESTING] remote lcd garbled data [FIXED] osd lib garbled screen on bmp_part [FIXED] grey_set_vp needs to return old viewport like lcd_set_viewport [FIXED] Viewport update now handles viewports with differing buffers/strides by copying to the main buffer [FIXED] splash on top of WPS leaves old framebuffer data (doesn't redraw) [UPDATE] refined this a bit more to have clear_viewport set the clean bit and have skin_render do its own screen clear scrolling viewports no longer trigger wps refresh also fixed a bug where guisyncyesno was displaying and then disappearing [ADDED!] New LCD macros that allow you to create properly size frame buffers in you desired size without wasting bytes (LCD_ and LCD_REMOTE_) LCD_STRIDE(w, h) same as STRIDE_MAIN LCD_FBSTRIDE(w, h) returns target specific stride for a buffer W x H LCD_NBELEMS(w, h) returns the number of fb_data sized elemenst needed for a buffer W x H LCD_NATIVE_STRIDE(s) conversion between rockbox native vertical and lcd native stride (2bitH) test_viewports.c has an example of usage [FIXED!!] 2bit targets don't respect non-native strides [FIXED] Few define snags Change-Id: I0d04c3834e464eca84a5a715743a297a0cefd0af
3916 lines
112 KiB
C
3916 lines
112 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st)
|
|
* Copyright (C) 2007 Nicolas Pennequin
|
|
* Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version)
|
|
*
|
|
* Original code: http://code.google.com/p/pictureflow/
|
|
*
|
|
* 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 "albumart.h"
|
|
#include "lib/read_image.h"
|
|
#include "lib/pluginlib_actions.h"
|
|
#include "lib/pluginlib_exit.h"
|
|
#include "lib/helper.h"
|
|
#include "lib/configfile.h"
|
|
#include "lib/grey.h"
|
|
#include "lib/mylcd.h"
|
|
#include "lib/feature_wrappers.h"
|
|
|
|
/******************************* Globals ***********************************/
|
|
static fb_data *lcd_fb;
|
|
|
|
/*
|
|
* Targets which use plugin_get_audio_buffer() can't have playback from
|
|
* within pictureflow itself, as the whole core audio buffer is occupied */
|
|
#define PF_PLAYBACK_CAPABLE (PLUGIN_BUFFER_SIZE > 0x10000)
|
|
|
|
#if PF_PLAYBACK_CAPABLE
|
|
#include "lib/playback_control.h"
|
|
#endif
|
|
|
|
#define PF_PREV ACTION_STD_PREV
|
|
#define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
|
|
#define PF_NEXT ACTION_STD_NEXT
|
|
#define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
|
|
#define PF_SELECT ACTION_STD_OK
|
|
#define PF_CONTEXT ACTION_STD_CONTEXT
|
|
#define PF_BACK ACTION_STD_CANCEL
|
|
#define PF_MENU ACTION_STD_MENU
|
|
#define PF_WPS ACTION_TREE_WPS
|
|
|
|
#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
|
|
#define USE_CORE_PREVNEXT
|
|
#endif
|
|
|
|
#ifndef USE_CORE_PREVNEXT
|
|
/* scrollwheel targets use the wheel, just as they do in lists,
|
|
* so there's no need for a special context,
|
|
* others use left/right here too (as oppsed to up/down in lists) */
|
|
const struct button_mapping pf_context_album_scroll[] =
|
|
{
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
{PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
|
|
{PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
|
|
{PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
|
|
{PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
|
|
#endif
|
|
#if (CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD)
|
|
{PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
|
|
{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 defined(BUTTON_LEFT) && defined(BUTTON_RIGHT)
|
|
{PF_PREV, BUTTON_LEFT, BUTTON_NONE},
|
|
{PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
|
|
{PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
|
|
{PF_NEXT_REPEAT, 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},
|
|
#else
|
|
#warning "LEFT/RIGHT not defined!"
|
|
#endif
|
|
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_PLUGIN|1)
|
|
};
|
|
#endif /* !USE_CORE_PREVNEXT */
|
|
|
|
const struct button_mapping pf_context_buttons[] =
|
|
{
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
{PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
|
|
{PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
|
|
#endif
|
|
#if CONFIG_KEYPAD == SANSA_C100_PAD
|
|
{PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
|
|
#elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
|
|
CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
|
|
CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
|
|
CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
|
|
CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD || \
|
|
CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD || CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD || \
|
|
CONFIG_KEYPAD == XDUOO_X3_PAD
|
|
{PF_QUIT, BUTTON_POWER, BUTTON_NONE},
|
|
#if CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
|
|
{PF_MENU, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_SELECT},
|
|
#endif
|
|
#elif CONFIG_KEYPAD == SANSA_FUZE_PAD
|
|
{PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE},
|
|
{PF_TRACKLIST, BUTTON_RIGHT, BUTTON_NONE},
|
|
/* These all use short press of BUTTON_POWER for menu, map long POWER to quit
|
|
*/
|
|
#elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
|
|
CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWON_D2_PAD
|
|
{PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
|
|
#if CONFIG_KEYPAD == COWON_D2_PAD
|
|
{PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
|
|
{ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
|
|
#endif
|
|
#elif CONFIG_KEYPAD == SANSA_E200_PAD
|
|
{PF_QUIT, BUTTON_POWER, BUTTON_NONE},
|
|
#elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
|
|
{PF_QUIT, BUTTON_EQ, BUTTON_NONE},
|
|
#elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
|
|
|| (CONFIG_KEYPAD == IPOD_3G_PAD) \
|
|
|| (CONFIG_KEYPAD == IPOD_4G_PAD) \
|
|
|| (CONFIG_KEYPAD == MPIO_HD300_PAD)
|
|
{PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
|
|
#elif CONFIG_KEYPAD == LOGIK_DAX_PAD
|
|
{PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
|
|
#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
|
|
{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
|
|
{PF_QUIT, BUTTON_OFF, BUTTON_NONE},
|
|
#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
|
|
{PF_QUIT, BUTTON_REC, BUTTON_NONE},
|
|
#elif CONFIG_KEYPAD == SAMSUNG_YH820_PAD || CONFIG_KEYPAD == SAMSUNG_YH92X_PAD
|
|
{PF_QUIT, BUTTON_REW|BUTTON_REPEAT, BUTTON_REW},
|
|
{PF_MENU, BUTTON_REW|BUTTON_REL, BUTTON_REW},
|
|
{PF_SELECT, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY},
|
|
{PF_CONTEXT, BUTTON_FFWD|BUTTON_REPEAT, BUTTON_FFWD},
|
|
{PF_TRACKLIST, BUTTON_FFWD|BUTTON_REL, BUTTON_FFWD},
|
|
{PF_WPS, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_PLAY},
|
|
#endif
|
|
#if CONFIG_KEYPAD == IAUDIO_M3_PAD
|
|
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
|
|
#else
|
|
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
|
|
#endif
|
|
};
|
|
const struct button_mapping *pf_contexts[] =
|
|
{
|
|
#ifndef USE_CORE_PREVNEXT
|
|
pf_context_album_scroll,
|
|
#endif
|
|
pf_context_buttons
|
|
};
|
|
|
|
#if LCD_DEPTH < 8
|
|
#if LCD_DEPTH > 1
|
|
#define N_BRIGHT(y) LCD_BRIGHTNESS(y)
|
|
#else /* LCD_DEPTH <= 1 */
|
|
#define N_BRIGHT(y) ((y > 127) ? 0 : 1)
|
|
#ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
|
|
#define PICTUREFLOW_DRMODE DRMODE_SOLID
|
|
#else
|
|
#define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
|
|
#endif
|
|
#endif /* LCD_DEPTH <= 1 */
|
|
#define USEGSLIB
|
|
GREY_INFO_STRUCT
|
|
#define LCD_BUF _grey_info.buffer
|
|
#define G_PIX(r,g,b) \
|
|
(77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
|
|
#define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
|
|
#define G_BRIGHT(y) (y)
|
|
#define BUFFER_WIDTH _grey_info.width
|
|
#define BUFFER_HEIGHT _grey_info.height
|
|
typedef unsigned char pix_t;
|
|
#else /* LCD_DEPTH >= 8 */
|
|
#define LCD_BUF lcd_fb
|
|
#define G_PIX LCD_RGBPACK
|
|
#define N_PIX LCD_RGBPACK
|
|
#define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
|
|
#define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
|
|
#define BUFFER_WIDTH LCD_WIDTH
|
|
#define BUFFER_HEIGHT LCD_HEIGHT
|
|
typedef fb_data pix_t;
|
|
#endif /* LCD_DEPTH >= 8 */
|
|
|
|
/* for fixed-point arithmetic, we need minimum 32-bit long
|
|
long long (64-bit) might be useful for multiplication and division */
|
|
#define PFreal long
|
|
#define PFREAL_SHIFT 10
|
|
#define PFREAL_FACTOR (1 << PFREAL_SHIFT)
|
|
#define PFREAL_ONE (1 << PFREAL_SHIFT)
|
|
#define PFREAL_HALF (PFREAL_ONE >> 1)
|
|
|
|
#define IANGLE_MAX 1024
|
|
#define IANGLE_MASK 1023
|
|
|
|
#define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
|
|
#define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
|
|
#define DISPLAY_HEIGHT REFLECT_TOP
|
|
#define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
|
|
LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
|
|
#define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
|
|
(REFLECT_HEIGHT * 5))
|
|
#define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
|
|
#define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
|
|
#define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
|
|
#define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
|
|
#define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
|
|
|
|
#define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
|
|
|
|
#define MAX_SLIDES_COUNT 10
|
|
|
|
#define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
|
|
#define CACHE_PREFIX PLUGIN_DEMOS_DATA_DIR "/pictureflow"
|
|
#define ALBUM_INDEX CACHE_PREFIX "/pictureflow_album.idx"
|
|
|
|
#define EV_EXIT 9999
|
|
#define EV_WAKEUP 1337
|
|
|
|
#define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
|
|
#define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
|
|
#define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
|
|
|
|
/* some magic numbers for cache_version. */
|
|
#define CACHE_REBUILD 0
|
|
#define CACHE_UPDATE 1
|
|
|
|
/* Error return values */
|
|
#define SUCCESS 0
|
|
#define ERROR_NO_ALBUMS -1
|
|
#define ERROR_BUFFER_FULL -2
|
|
#define ERROR_NO_ARTISTS -3
|
|
#define ERROR_USER_ABORT -4
|
|
|
|
/* current version for cover cache */
|
|
#define CACHE_VERSION 3
|
|
#define CONFIG_VERSION 1
|
|
#define CONFIG_FILE "pictureflow.cfg"
|
|
#define INDEX_HDR "PFID"
|
|
|
|
/** structs we use */
|
|
struct pf_config_t
|
|
{
|
|
/* config values */
|
|
int slide_spacing;
|
|
int center_margin;
|
|
|
|
int num_slides;
|
|
int zoom;
|
|
|
|
int auto_wps;
|
|
int last_album;
|
|
|
|
int backlight_mode;
|
|
int cache_version;
|
|
|
|
int show_album_name;
|
|
bool resize;
|
|
bool show_fps;
|
|
};
|
|
|
|
struct pf_index_t {
|
|
uint32_t header; /*INDEX_HDR*/
|
|
uint16_t artist_ct;
|
|
uint16_t album_ct;
|
|
|
|
char *artist_names;
|
|
struct artist_data *artist_index;
|
|
size_t artist_len;
|
|
|
|
unsigned int album_untagged_idx;
|
|
char *album_names;
|
|
struct album_data *album_index;
|
|
size_t album_len;
|
|
long album_untagged_seek;
|
|
|
|
void * buf;
|
|
size_t buf_sz;
|
|
};
|
|
|
|
struct pf_track_t {
|
|
int count;
|
|
int cur_idx;
|
|
int sel;
|
|
int sel_pulse;
|
|
int last_sel;
|
|
int list_start;
|
|
int list_visible;
|
|
int list_y;
|
|
int list_h;
|
|
size_t borrowed;
|
|
struct track_data *index;
|
|
char *names;
|
|
};
|
|
|
|
struct albumart_t {
|
|
struct bitmap input_bmp;
|
|
char pfraw_file[MAX_PATH];
|
|
char file[MAX_PATH];
|
|
int idx;
|
|
int slides;
|
|
int inspected;
|
|
void * buf;
|
|
size_t buf_sz;
|
|
};
|
|
|
|
struct slide_data {
|
|
int slide_index;
|
|
int angle;
|
|
PFreal cx;
|
|
PFreal cy;
|
|
PFreal distance;
|
|
};
|
|
|
|
struct slide_cache {
|
|
int index; /* index of the cached slide */
|
|
int hid; /* handle ID of the cached slide */
|
|
short next; /* "next" slide, with LRU last */
|
|
short prev; /* "previous" slide */
|
|
};
|
|
|
|
struct album_data {
|
|
int name_idx; /* offset to the album name */
|
|
int artist_idx; /* offset to the artist name */
|
|
long artist_seek; /* artist taglist position */
|
|
long seek; /* album taglist position */
|
|
};
|
|
|
|
struct artist_data {
|
|
int name_idx; /* offset to the artist name */
|
|
long seek; /* artist taglist position */
|
|
};
|
|
|
|
struct track_data {
|
|
uint32_t sort;
|
|
int name_idx; /* offset to the track name */
|
|
long seek;
|
|
#if PF_PLAYBACK_CAPABLE
|
|
/* offset to the filename in the string, needed for playlist generation */
|
|
int filename_idx;
|
|
#endif
|
|
};
|
|
|
|
struct rect {
|
|
int left;
|
|
int right;
|
|
int top;
|
|
int bottom;
|
|
};
|
|
|
|
struct load_slide_event_data {
|
|
int slide_index;
|
|
int cache_index;
|
|
};
|
|
|
|
struct pf_slide_cache
|
|
{
|
|
struct slide_cache cache[SLIDE_CACHE_SIZE];
|
|
int free;
|
|
int used;
|
|
int left_idx;
|
|
int right_idx;
|
|
int center_idx;
|
|
};
|
|
|
|
enum pf_scroll_line_type {
|
|
PF_SCROLL_TRACK = 0,
|
|
PF_SCROLL_ALBUM,
|
|
PF_SCROLL_ARTIST,
|
|
PF_MAX_SCROLL_LINES
|
|
};
|
|
|
|
struct pf_scroll_line_info {
|
|
long ticks; /* number of ticks between each move */
|
|
long delay; /* number of ticks to delay starting scrolling */
|
|
int step; /* pixels to move */
|
|
long next_scroll; /* tick of the next move */
|
|
};
|
|
|
|
struct pf_scroll_line {
|
|
int width; /* width of the string */
|
|
int offset; /* x coordinate of the string */
|
|
int step; /* 0 if scroll is disabled. otherwise, pixels to move */
|
|
long start_tick; /* tick when to start scrolling */
|
|
};
|
|
|
|
struct pfraw_header {
|
|
int32_t width; /* bmap width in pixels */
|
|
int32_t height; /* bmap height in pixels */
|
|
};
|
|
|
|
enum show_album_name_values {
|
|
ALBUM_NAME_HIDE = 0,
|
|
ALBUM_NAME_BOTTOM,
|
|
ALBUM_NAME_TOP,
|
|
ALBUM_AND_ARTIST_TOP,
|
|
ALBUM_AND_ARTIST_BOTTOM
|
|
};
|
|
static char* show_album_name_conf[] =
|
|
{
|
|
"hide",
|
|
"bottom",
|
|
"top",
|
|
"both top",
|
|
"both bottom",
|
|
};
|
|
|
|
#define MAX_SPACING 40
|
|
#define MAX_MARGIN 80
|
|
|
|
static struct albumart_t aa_cache;
|
|
static struct pf_config_t pf_cfg;
|
|
|
|
static struct configdata config[] =
|
|
{
|
|
{ TYPE_INT, 0, MAX_SPACING, { .int_p = &pf_cfg.slide_spacing }, "slide spacing",
|
|
NULL },
|
|
{ TYPE_INT, 0, MAX_MARGIN, { .int_p = &pf_cfg.center_margin }, "center margin",
|
|
NULL },
|
|
{ TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &pf_cfg.num_slides }, "slides count",
|
|
NULL },
|
|
{ TYPE_INT, 0, 300, { .int_p = &pf_cfg.zoom }, "zoom", NULL },
|
|
{ TYPE_BOOL, 0, 1, { .bool_p = &pf_cfg.show_fps }, "show fps", NULL },
|
|
{ TYPE_BOOL, 0, 1, { .bool_p = &pf_cfg.resize }, "resize", NULL },
|
|
{ TYPE_INT, 0, 100, { .int_p = &pf_cfg.cache_version }, "cache version", NULL },
|
|
{ TYPE_ENUM, 0, 5, { .int_p = &pf_cfg.show_album_name }, "show album name",
|
|
show_album_name_conf },
|
|
{ TYPE_INT, 0, 2, { .int_p = &pf_cfg.auto_wps }, "auto wps", NULL },
|
|
{ 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 },
|
|
};
|
|
|
|
#define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
|
|
|
|
/** below we allocate the memory we want to use **/
|
|
|
|
static pix_t *buffer; /* for now it always points to the lcd framebuffer */
|
|
static uint8_t reflect_table[REFLECT_HEIGHT];
|
|
static struct slide_data center_slide;
|
|
static struct slide_data left_slides[MAX_SLIDES_COUNT];
|
|
static struct slide_data right_slides[MAX_SLIDES_COUNT];
|
|
static int slide_frame;
|
|
static int step;
|
|
static int target;
|
|
static int fade;
|
|
static int center_index = 0; /* index of the slide that is in the center */
|
|
static int itilt;
|
|
static PFreal offsetX;
|
|
static PFreal offsetY;
|
|
static int number_of_slides;
|
|
|
|
static struct pf_slide_cache pf_sldcache;
|
|
|
|
/* use long for aligning */
|
|
unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
|
|
/* queue (as array) for scheduling load_surface */
|
|
|
|
static int empty_slide_hid;
|
|
|
|
unsigned int thread_id;
|
|
struct event_queue thread_q;
|
|
|
|
static struct tagcache_search tcs;
|
|
|
|
static struct buflib_context buf_ctx;
|
|
|
|
static struct pf_index_t pf_idx;
|
|
|
|
static struct pf_track_t pf_tracks;
|
|
|
|
void reset_track_list(void);
|
|
|
|
static bool thread_is_running;
|
|
|
|
static int cover_animation_keyframe;
|
|
static int extra_fade;
|
|
|
|
static struct pf_scroll_line_info scroll_line_info;
|
|
static struct pf_scroll_line scroll_lines[PF_MAX_SCROLL_LINES];
|
|
|
|
enum ePFS{ePFS_ARTIST = 0, ePFS_ALBUM};
|
|
/*
|
|
Proposals for transitions:
|
|
|
|
pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
|
|
-> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
|
|
|
|
pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
|
|
|
|
pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
|
|
|
|
TODO:
|
|
pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
|
|
pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
|
|
|
|
pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
|
|
*/
|
|
enum pf_states {
|
|
pf_idle = 0,
|
|
pf_scrolling,
|
|
pf_cover_in,
|
|
pf_show_tracks,
|
|
pf_cover_out
|
|
};
|
|
|
|
static int pf_state;
|
|
|
|
/** code */
|
|
static bool free_slide_prio(int prio);
|
|
bool load_new_slide(void);
|
|
int load_surface(int);
|
|
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 bool check_database(bool prompt)
|
|
{
|
|
bool needwarn = true;
|
|
int spin = 5;
|
|
|
|
struct tagcache_stat *stat = rb->tagcache_get_stat();
|
|
|
|
while ( !(stat->initialized && stat->ready) )
|
|
{
|
|
if (--spin > 0)
|
|
{
|
|
rb->sleep(HZ/5);
|
|
}
|
|
else if (needwarn)
|
|
{
|
|
needwarn = false;
|
|
rb->splash(0, ID2P(LANG_TAGCACHE_BUSY));
|
|
}
|
|
else if (!prompt)
|
|
return false;
|
|
else if (rb->action_userabort(HZ/5))
|
|
return false;
|
|
|
|
rb->yield();
|
|
stat = rb->tagcache_get_stat();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool confirm_quit(void)
|
|
{
|
|
const struct text_message prompt =
|
|
{ (const char*[]) {"Quit?", "Progress will be lost"}, 2};
|
|
enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL);
|
|
while (rb->button_get(false) == BUTTON_NONE)
|
|
;;
|
|
|
|
if(response == YESNO_NO)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
static void config_save(int cache_version)
|
|
{
|
|
pf_cfg.cache_version = cache_version;
|
|
configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
|
|
}
|
|
|
|
static void config_set_defaults(struct pf_config_t *cfg)
|
|
{
|
|
cfg->slide_spacing = DISPLAY_WIDTH / 4;
|
|
cfg->center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
|
|
cfg->num_slides = 4;
|
|
cfg->zoom = 100;
|
|
cfg->show_fps = false;
|
|
cfg->auto_wps = 0;
|
|
cfg->last_album = 0;
|
|
cfg->backlight_mode = 0;
|
|
cfg->resize = true;
|
|
cfg->cache_version = 0;
|
|
cfg->show_album_name = (LCD_HEIGHT > 100)
|
|
? ALBUM_NAME_TOP : ALBUM_NAME_BOTTOM;
|
|
}
|
|
|
|
static inline PFreal fmul(PFreal a, PFreal b)
|
|
{
|
|
return (a*b) >> PFREAL_SHIFT;
|
|
}
|
|
|
|
/**
|
|
* This version preshifts each operand, which is useful when we know how many
|
|
* of the least significant bits will be empty, or are worried about overflow
|
|
* in a particular calculation
|
|
*/
|
|
static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
|
|
{
|
|
return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
|
|
}
|
|
|
|
/* ARMv5+ has a clz instruction equivalent to our function.
|
|
*/
|
|
#if (defined(CPU_ARM) && (ARM_ARCH > 4))
|
|
static inline int clz(uint32_t v)
|
|
{
|
|
return __builtin_clz(v);
|
|
}
|
|
|
|
/* Otherwise, use our clz, which can be inlined */
|
|
#elif defined(CPU_COLDFIRE)
|
|
/* This clz is based on the log2(n) implementation at
|
|
* http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
|
|
* A clz benchmark plugin showed this to be about 14% faster on coldfire
|
|
* than the LUT-based version.
|
|
*/
|
|
static inline int clz(uint32_t v)
|
|
{
|
|
int r = 32;
|
|
if (v >= 0x10000)
|
|
{
|
|
v >>= 16;
|
|
r -= 16;
|
|
}
|
|
if (v & 0xff00)
|
|
{
|
|
v >>= 8;
|
|
r -= 8;
|
|
}
|
|
if (v & 0xf0)
|
|
{
|
|
v >>= 4;
|
|
r -= 4;
|
|
}
|
|
if (v & 0xc)
|
|
{
|
|
v >>= 2;
|
|
r -= 2;
|
|
}
|
|
if (v & 2)
|
|
{
|
|
v >>= 1;
|
|
r -= 1;
|
|
}
|
|
r -= v;
|
|
return r;
|
|
}
|
|
#else
|
|
static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
|
|
0, 0, 0, 0, 0, 0, 0, 0 };
|
|
/* This clz is based on the log2(n) implementation at
|
|
* http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
|
|
* It is not any faster than the one above, but trades 16B in the lookup table
|
|
* for a savings of 12B per each inlined call.
|
|
*/
|
|
static inline int clz(uint32_t v)
|
|
{
|
|
int r = 28;
|
|
if (v >= 0x10000)
|
|
{
|
|
v >>= 16;
|
|
r -= 16;
|
|
}
|
|
if (v & 0xff00)
|
|
{
|
|
v >>= 8;
|
|
r -= 8;
|
|
}
|
|
if (v & 0xf0)
|
|
{
|
|
v >>= 4;
|
|
r -= 4;
|
|
}
|
|
return r + clz_lut[v];
|
|
}
|
|
#endif
|
|
|
|
/* Return the maximum possible left shift for a signed int32, without
|
|
* overflow
|
|
*/
|
|
static inline int allowed_shift(int32_t val)
|
|
{
|
|
uint32_t uval = val ^ (val >> 31);
|
|
return clz(uval) - 1;
|
|
}
|
|
|
|
/* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
|
|
* num and den before dividing.
|
|
*/
|
|
static inline PFreal fdiv(PFreal num, PFreal den)
|
|
{
|
|
int shift = allowed_shift(num);
|
|
shift = MIN(PFREAL_SHIFT, shift);
|
|
num <<= shift;
|
|
den >>= PFREAL_SHIFT - shift;
|
|
return num / den;
|
|
}
|
|
|
|
#define fmin(a,b) (((a) < (b)) ? (a) : (b))
|
|
#define fmax(a,b) (((a) > (b)) ? (a) : (b))
|
|
#define fabs(a) (a < 0 ? -a : a)
|
|
#define fbound(min,val,max) (fmax((min),fmin((max),(val))))
|
|
|
|
#define MULUQ(a, b) ((a) * (b))
|
|
|
|
#if 0
|
|
#define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
|
|
#define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
|
|
|
|
#define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
|
|
#define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
|
|
|
|
static inline PFreal fmul(PFreal a, PFreal b)
|
|
{
|
|
return (a*b) >> PFREAL_SHIFT;
|
|
}
|
|
|
|
static inline PFreal fdiv(PFreal n, PFreal m)
|
|
{
|
|
return (n<<(PFREAL_SHIFT))/m;
|
|
}
|
|
#endif
|
|
|
|
/* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
|
|
static const short sin_tab[] = {
|
|
0, 100, 200, 297, 392, 483, 569, 650,
|
|
724, 792, 851, 903, 946, 980, 1004, 1019,
|
|
1024, 1019, 1004, 980, 946, 903, 851, 792,
|
|
724, 650, 569, 483, 392, 297, 200, 100,
|
|
0, -100, -200, -297, -392, -483, -569, -650,
|
|
-724, -792, -851, -903, -946, -980, -1004, -1019,
|
|
-1024, -1019, -1004, -980, -946, -903, -851, -792,
|
|
-724, -650, -569, -483, -392, -297, -200, -100,
|
|
0
|
|
};
|
|
|
|
static inline PFreal fsin(int iangle)
|
|
{
|
|
iangle &= IANGLE_MASK;
|
|
|
|
int i = (iangle >> 4);
|
|
PFreal p = sin_tab[i];
|
|
PFreal q = sin_tab[(i+1)];
|
|
PFreal g = (q - p);
|
|
return p + g * (iangle-i*16)/16;
|
|
}
|
|
|
|
static inline PFreal fcos(int iangle)
|
|
{
|
|
return fsin(iangle + (IANGLE_MAX >> 2));
|
|
}
|
|
|
|
/* scales the 8bit subpixel value to native lcd format, indicated by bits */
|
|
static inline unsigned scale_subpixel_lcd(unsigned val, unsigned bits)
|
|
{
|
|
(void) bits;
|
|
#if LCD_PIXELFORMAT != RGB888
|
|
val = val * ((1 << bits) - 1);
|
|
val = ((val >> 8) + val + 128) >> 8;
|
|
#endif
|
|
return val;
|
|
}
|
|
|
|
static void output_row_8_transposed(uint32_t row, void * row_in,
|
|
struct scaler_context *ctx)
|
|
{
|
|
pix_t *dest = (pix_t*)ctx->bm->data + row;
|
|
pix_t *end = dest + ctx->bm->height * ctx->bm->width;
|
|
#ifdef USEGSLIB
|
|
uint8_t *qp = (uint8_t*)row_in;
|
|
for (; dest < end; dest += ctx->bm->height)
|
|
*dest = *qp++;
|
|
#else
|
|
struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
|
|
unsigned r, g, b;
|
|
for (; dest < end; dest += ctx->bm->height)
|
|
{
|
|
r = scale_subpixel_lcd(qp->red, 5);
|
|
g = scale_subpixel_lcd(qp->green, 6);
|
|
b = scale_subpixel_lcd((qp++)->blue, 5);
|
|
*dest = FB_RGBPACK_LCD(r,g,b);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* read_image_file() is called without FORMAT_TRANSPARENT so
|
|
* it's safe to ignore alpha channel in the next two functions */
|
|
static void output_row_32_transposed(uint32_t row, void * row_in,
|
|
struct scaler_context *ctx)
|
|
{
|
|
pix_t *dest = (pix_t*)ctx->bm->data + row;
|
|
pix_t *end = dest + ctx->bm->height * ctx->bm->width;
|
|
#ifdef USEGSLIB
|
|
uint32_t *qp = (uint32_t*)row_in;
|
|
for (; dest < end; dest += ctx->bm->height)
|
|
*dest = SC_OUT(*qp++, ctx);
|
|
#else
|
|
struct uint32_argb *qp = (struct uint32_argb*)row_in;
|
|
int r, g, b;
|
|
for (; dest < end; dest += ctx->bm->height)
|
|
{
|
|
r = scale_subpixel_lcd(SC_OUT(qp->r, ctx), 5);
|
|
g = scale_subpixel_lcd(SC_OUT(qp->g, ctx), 6);
|
|
b = scale_subpixel_lcd(SC_OUT(qp->b, ctx), 5);
|
|
qp++;
|
|
*dest = FB_RGBPACK_LCD(r,g,b);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_LCD_COLOR
|
|
static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
|
|
struct scaler_context *ctx)
|
|
{
|
|
pix_t *dest = (pix_t*)ctx->bm->data + row;
|
|
pix_t *end = dest + ctx->bm->height * ctx->bm->width;
|
|
struct uint32_argb *qp = (struct uint32_argb*)row_in;
|
|
for (; dest < end; dest += ctx->bm->height)
|
|
{
|
|
unsigned r, g, b, y, u, v;
|
|
y = SC_OUT(qp->b, ctx);
|
|
u = SC_OUT(qp->g, ctx);
|
|
v = SC_OUT(qp->r, ctx);
|
|
qp++;
|
|
yuv_to_rgb(y, u, v, &r, &g, &b);
|
|
r = scale_subpixel_lcd(r, 5);
|
|
g = scale_subpixel_lcd(g, 6);
|
|
b = scale_subpixel_lcd(b, 5);
|
|
*dest = FB_RGBPACK_LCD(r, g, b);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static unsigned int get_size(struct bitmap *bm)
|
|
{
|
|
return bm->width * bm->height * sizeof(pix_t);
|
|
}
|
|
|
|
const struct custom_format format_transposed = {
|
|
.output_row_8 = output_row_8_transposed,
|
|
#ifdef HAVE_LCD_COLOR
|
|
.output_row_32 = {
|
|
output_row_32_transposed,
|
|
output_row_32_transposed_fromyuv
|
|
},
|
|
#else
|
|
.output_row_32 = output_row_32_transposed,
|
|
#endif
|
|
.get_size = get_size
|
|
};
|
|
|
|
static const struct button_mapping* get_context_map(int context)
|
|
{
|
|
return pf_contexts[context & ~CONTEXT_PLUGIN];
|
|
}
|
|
|
|
/* scrolling */
|
|
static void init_scroll_lines(void)
|
|
{
|
|
int i;
|
|
static const char scroll_tick_table[16] = {
|
|
/* Hz values:
|
|
1, 1.25, 1.55, 2, 2.5, 3.12, 4, 5, 6.25, 8.33, 10, 12.5, 16.7, 20, 25, 33 */
|
|
100, 80, 64, 50, 40, 32, 25, 20, 16, 12, 10, 8, 6, 5, 4, 3
|
|
};
|
|
|
|
scroll_line_info.ticks = scroll_tick_table[rb->global_settings->scroll_speed];
|
|
scroll_line_info.step = rb->global_settings->scroll_step;
|
|
scroll_line_info.delay = rb->global_settings->scroll_delay / (HZ / 10);
|
|
scroll_line_info.next_scroll = *rb->current_tick;
|
|
for (i = 0; i < PF_MAX_SCROLL_LINES; i++)
|
|
scroll_lines[i].step = 0;
|
|
}
|
|
|
|
static void set_scroll_line(const char *str, enum pf_scroll_line_type type)
|
|
{
|
|
struct pf_scroll_line *s = &scroll_lines[type];
|
|
s->width = mylcd_getstringsize(str, NULL, NULL);
|
|
s->step = 0;
|
|
s->offset = 0;
|
|
s->start_tick = *rb->current_tick + scroll_line_info.delay;
|
|
if (LCD_WIDTH - s->width < 0)
|
|
s->step = scroll_line_info.step;
|
|
else
|
|
s->offset = (LCD_WIDTH - s->width) / 2;
|
|
}
|
|
|
|
static int get_scroll_line_offset(enum pf_scroll_line_type type)
|
|
{
|
|
return scroll_lines[type].offset;
|
|
}
|
|
|
|
static void update_scroll_lines(void)
|
|
{
|
|
int i;
|
|
|
|
if (TIME_BEFORE(*rb->current_tick, scroll_line_info.next_scroll))
|
|
return;
|
|
|
|
scroll_line_info.next_scroll = *rb->current_tick + scroll_line_info.ticks;
|
|
|
|
for (i = 0; i < PF_MAX_SCROLL_LINES; i++)
|
|
{
|
|
struct pf_scroll_line *s = &scroll_lines[i];
|
|
if (s->step && TIME_BEFORE(s->start_tick, *rb->current_tick))
|
|
{
|
|
s->offset -= s->step;
|
|
|
|
if (s->offset >= 0) {
|
|
/* at beginning of line */
|
|
s->offset = 0;
|
|
s->step = scroll_line_info.step;
|
|
s->start_tick = *rb->current_tick + scroll_line_info.delay * 2;
|
|
}
|
|
if (s->offset <= LCD_WIDTH - s->width) {
|
|
/* at end of line */
|
|
s->offset = LCD_WIDTH - s->width;
|
|
s->step = -scroll_line_info.step;
|
|
s->start_tick = *rb->current_tick + scroll_line_info.delay * 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Create the lookup table with the scaling values for the reflections */
|
|
static void init_reflect_table(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < REFLECT_HEIGHT; i++)
|
|
reflect_table[i] =
|
|
(768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
|
|
(5 * REFLECT_HEIGHT);
|
|
}
|
|
|
|
static int compare_album_artists (const void *a_v, const void *b_v)
|
|
{
|
|
uint32_t a = ((struct album_data *)a_v)->artist_idx;
|
|
uint32_t b = ((struct album_data *)b_v)->artist_idx;
|
|
return (int)(a - b);
|
|
}
|
|
|
|
static void write_album_index(int idx, int name_idx,
|
|
long album_seek, int artist_idx, long artist_seek)
|
|
{
|
|
pf_idx.album_index[idx].name_idx = name_idx;
|
|
pf_idx.album_index[idx].seek = album_seek;
|
|
pf_idx.album_index[idx].artist_idx = artist_idx;
|
|
pf_idx.album_index[idx].artist_seek = artist_seek;
|
|
}
|
|
|
|
static inline void write_album_entry(struct tagcache_search *tcs,
|
|
int name_idx, unsigned int len)
|
|
{
|
|
write_album_index(-pf_idx.album_ct, name_idx, tcs->result_seek, 0, -1);
|
|
pf_idx.album_len += len;
|
|
pf_idx.album_ct++;
|
|
|
|
if (pf_idx.album_untagged_seek == -1 && rb->strcmp(UNTAGGED, tcs->result) == 0)
|
|
{
|
|
pf_idx.album_untagged_idx = name_idx;
|
|
pf_idx.album_untagged_seek = tcs->result_seek;
|
|
}
|
|
}
|
|
|
|
static void write_artist_entry(struct tagcache_search *tcs,
|
|
int name_idx, unsigned int len)
|
|
{
|
|
pf_idx.artist_index[-pf_idx.artist_ct].name_idx = name_idx;
|
|
pf_idx.artist_index[-pf_idx.artist_ct].seek = tcs->result_seek;
|
|
pf_idx.artist_len += len;
|
|
pf_idx.artist_ct++;
|
|
}
|
|
|
|
/* adds tagcache_search results into artist/album index */
|
|
static int get_tcs_search_res(int type, struct tagcache_search *tcs,
|
|
void **buf, size_t *bufsz)
|
|
{
|
|
int ret = SUCCESS;
|
|
unsigned int l, name_idx = 0;
|
|
void (*writefn)(struct tagcache_search *, int, unsigned int);
|
|
int data_size;
|
|
if (type == ePFS_ARTIST)
|
|
{
|
|
writefn = &write_artist_entry;
|
|
data_size = sizeof(struct artist_data);
|
|
}
|
|
else
|
|
{
|
|
writefn = &write_album_entry;
|
|
data_size = sizeof(struct album_data);
|
|
}
|
|
|
|
while (rb->tagcache_get_next(tcs))
|
|
{
|
|
if (rb->button_get(false) > BUTTON_NONE)
|
|
{
|
|
if (confirm_quit())
|
|
{
|
|
ret = ERROR_USER_ABORT;
|
|
break;
|
|
} else
|
|
rb->lcd_clear_display();
|
|
}
|
|
|
|
*bufsz -= data_size;
|
|
|
|
l = tcs->result_len;
|
|
|
|
if ( l > *bufsz )
|
|
{
|
|
/* not enough memory */
|
|
ret = ERROR_BUFFER_FULL;
|
|
break;
|
|
}
|
|
|
|
rb->strcpy(*buf, tcs->result);
|
|
|
|
*bufsz -= l;
|
|
*buf = l + (char *)*buf;
|
|
|
|
writefn(tcs, name_idx, l);
|
|
|
|
name_idx += l;
|
|
}
|
|
rb->tagcache_search_finish(tcs);
|
|
return ret;
|
|
}
|
|
|
|
/*adds <untagged> albums/artist to existing album index */
|
|
static int create_album_untagged(struct tagcache_search *tcs,
|
|
void **buf, size_t *bufsz)
|
|
{
|
|
int ret = SUCCESS;
|
|
int album_count = pf_idx.album_ct; /* store existing count */
|
|
int total_count = pf_idx.album_ct + pf_idx.artist_ct * 2;
|
|
long seek;
|
|
int last, final, retry;
|
|
int i, j;
|
|
draw_splashscreen(*buf, *bufsz);
|
|
draw_progressbar(0, total_count, "Searching " UNTAGGED);
|
|
|
|
/* 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);
|
|
|
|
while (rb->tagcache_get_next(tcs))
|
|
{
|
|
if (rb->button_get(false) > BUTTON_NONE) {
|
|
if (confirm_quit())
|
|
return ERROR_USER_ABORT;
|
|
else
|
|
{
|
|
rb->lcd_clear_display();
|
|
draw_progressbar(pf_idx.album_ct, total_count,
|
|
"Searching " UNTAGGED);
|
|
}
|
|
}
|
|
|
|
if (tcs->result_seek ==
|
|
pf_idx.album_index[-(pf_idx.album_ct - 1)].artist_seek)
|
|
continue;
|
|
|
|
if (sizeof(struct album_data) > *bufsz)
|
|
{
|
|
/* not enough memory */
|
|
ret = ERROR_BUFFER_FULL;
|
|
break;
|
|
}
|
|
|
|
*bufsz -= sizeof(struct album_data);
|
|
write_album_index(-pf_idx.album_ct, pf_idx.album_untagged_idx,
|
|
pf_idx.album_untagged_seek, -1, tcs->result_seek);
|
|
|
|
pf_idx.album_ct++;
|
|
draw_progressbar(pf_idx.album_ct, total_count, NULL);
|
|
}
|
|
rb->tagcache_search_finish(tcs);
|
|
|
|
if (ret == SUCCESS) {
|
|
draw_splashscreen(*buf, *bufsz);
|
|
draw_progressbar(0, pf_idx.album_ct, "Finalizing " UNTAGGED);
|
|
|
|
last = 0;
|
|
final = pf_idx.artist_ct;
|
|
retry = 0;
|
|
|
|
/* map the artist_seek position to the artist name index */
|
|
for (j = album_count; j < pf_idx.album_ct; j++)
|
|
{
|
|
if (rb->button_get(false) > BUTTON_NONE) {
|
|
if (confirm_quit())
|
|
return ERROR_USER_ABORT;
|
|
else
|
|
{
|
|
rb->lcd_clear_display();
|
|
draw_progressbar(j, pf_idx.album_ct, "Finalizing " UNTAGGED);
|
|
}
|
|
}
|
|
|
|
draw_progressbar(j, pf_idx.album_ct, NULL);
|
|
seek = pf_idx.album_index[-j].artist_seek;
|
|
|
|
retry_artist_lookup:
|
|
retry++;
|
|
for (i = last; i < final; i++)
|
|
{
|
|
if (seek == pf_idx.artist_index[i].seek)
|
|
{
|
|
int idx = pf_idx.artist_index[i].name_idx;
|
|
pf_idx.album_index[-j].artist_idx = idx;
|
|
last = i; /* last match, start here next loop */
|
|
final = pf_idx.artist_ct;
|
|
retry = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (retry > 0 && retry < 2)
|
|
{
|
|
/* no match start back at beginning */
|
|
final = last;
|
|
last = 0;
|
|
goto retry_artist_lookup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Create an index of all artists from the database */
|
|
static int build_artist_index(struct tagcache_search *tcs,
|
|
void **buf, size_t *bufsz)
|
|
{
|
|
int i, res = SUCCESS;
|
|
struct artist_data* tmp_artist;
|
|
|
|
/* artist index starts at end of buf it will be rearranged when finalized */
|
|
pf_idx.artist_index = ((struct artist_data *)(*bufsz + (char *) *buf)) - 1;
|
|
pf_idx.artist_ct = 0;
|
|
pf_idx.artist_len = 0;
|
|
/* artist names starts at beginning of buf */
|
|
pf_idx.artist_names = *buf;
|
|
|
|
rb->tagcache_search(tcs, tag_albumartist);
|
|
res = get_tcs_search_res(ePFS_ARTIST, tcs, &(*buf), bufsz);
|
|
rb->tagcache_search_finish(tcs);
|
|
if (res < SUCCESS)
|
|
return res;
|
|
|
|
ALIGN_BUFFER(*buf, *bufsz, 4);
|
|
|
|
/* finalize the artist index */
|
|
tmp_artist = (struct artist_data*)*buf;
|
|
for (i = pf_idx.artist_ct - 1; i >= 0; i--)
|
|
tmp_artist[i] = pf_idx.artist_index[-i];
|
|
|
|
pf_idx.artist_index = tmp_artist;
|
|
/* move buf ptr to end of artist_index */
|
|
*buf = pf_idx.artist_index + pf_idx.artist_ct;
|
|
ALIGN_BUFFER(*buf, *bufsz, 4);
|
|
|
|
if (res == SUCCESS)
|
|
{
|
|
if (pf_idx.artist_ct > 0)
|
|
res = pf_idx.artist_ct;
|
|
else
|
|
res = ERROR_NO_ALBUMS;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
Create an index of all artists and albums from the database.
|
|
Also store the artists and album names so we can access them later.
|
|
*/
|
|
static int create_album_index(void)
|
|
{
|
|
void *buf = pf_idx.buf;
|
|
size_t buf_size = pf_idx.buf_sz;
|
|
|
|
struct album_data* tmp_album;
|
|
|
|
int i, j, last, final, retry, res;
|
|
|
|
draw_splashscreen(buf, buf_size);
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
|
|
/* Artists */
|
|
res = build_artist_index(&tcs, &buf, &buf_size);
|
|
if (res < SUCCESS)
|
|
return res;
|
|
|
|
/* Albums */
|
|
pf_idx.album_ct = 0;
|
|
pf_idx.album_len =0;
|
|
pf_idx.album_untagged_idx = 0;
|
|
pf_idx.album_untagged_seek = -1;
|
|
|
|
/* album_index starts at end of buf it will be rearranged when finalized */
|
|
pf_idx.album_index = ((struct album_data *)(buf_size + (char *)buf)) - 1;
|
|
/* album_names starts at the beginning of buf */
|
|
pf_idx.album_names = buf;
|
|
|
|
rb->tagcache_search(&tcs, tag_album);
|
|
res = get_tcs_search_res(ePFS_ALBUM, &tcs, &buf, &buf_size);
|
|
rb->tagcache_search_finish(&tcs);
|
|
if (res < SUCCESS)
|
|
return res;
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
|
|
/* Build artist list for untagged albums */
|
|
res = create_album_untagged(&tcs, &buf, &buf_size);
|
|
|
|
if (res < SUCCESS)
|
|
return res;
|
|
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
|
|
/* finalize the album index */
|
|
tmp_album = (struct album_data*)buf;
|
|
for (i = pf_idx.album_ct - 1; i >= 0; i--)
|
|
tmp_album[i] = pf_idx.album_index[-i];
|
|
|
|
pf_idx.album_index = tmp_album;
|
|
/* move buf ptr to end of album_index */
|
|
buf = pf_idx.album_index + pf_idx.album_ct;
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
|
|
/* Assign indices */
|
|
draw_splashscreen(buf, buf_size);
|
|
draw_progressbar(0, pf_idx.album_ct, "Assigning Albums");
|
|
for (j = 0; j < pf_idx.album_ct; j++)
|
|
{
|
|
if (rb->button_get(false) > BUTTON_NONE)
|
|
{
|
|
if (confirm_quit())
|
|
return ERROR_USER_ABORT;
|
|
else
|
|
{
|
|
rb->lcd_clear_display();
|
|
draw_progressbar(j, pf_idx.album_ct, "Assigning Albums");
|
|
}
|
|
|
|
}
|
|
|
|
draw_progressbar(j, pf_idx.album_ct, NULL);
|
|
if (pf_idx.album_index[j].artist_seek >= 0) { continue; }
|
|
|
|
rb->tagcache_search(&tcs, tag_albumartist);
|
|
rb->tagcache_search_add_filter(&tcs, tag_album, pf_idx.album_index[j].seek);
|
|
|
|
last = 0;
|
|
final = pf_idx.artist_ct;
|
|
retry = 0;
|
|
if (rb->tagcache_get_next(&tcs))
|
|
{
|
|
|
|
retry_artist_lookup:
|
|
retry++;
|
|
for (i = last; i < final; i++)
|
|
{
|
|
if (tcs.result_seek == pf_idx.artist_index[i].seek)
|
|
{
|
|
int idx = pf_idx.artist_index[i].name_idx;
|
|
pf_idx.album_index[j].artist_idx = idx;
|
|
pf_idx.album_index[j].artist_seek = tcs.result_seek;
|
|
last = i; /* last match, start here next loop */
|
|
final = pf_idx.artist_ct;
|
|
retry = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (retry > 0 && retry < 2)
|
|
{
|
|
/* no match start back at beginning */
|
|
final = last;
|
|
last = 0;
|
|
goto retry_artist_lookup;
|
|
}
|
|
}
|
|
rb->tagcache_search_finish(&tcs);
|
|
}
|
|
/* sort list order to find duplicates */
|
|
rb->qsort(pf_idx.album_index, pf_idx.album_ct,
|
|
sizeof(struct album_data), compare_album_artists);
|
|
|
|
draw_splashscreen(buf, buf_size);
|
|
draw_progressbar(0, pf_idx.album_ct, "Removing duplicates");
|
|
/* mark duplicate albums for deletion */
|
|
for (i = 0; i < pf_idx.album_ct - 1; i++) /* -1 don't check last entry */
|
|
{
|
|
int idxi = pf_idx.album_index[i].artist_idx;
|
|
int seeki = pf_idx.album_index[i].seek;
|
|
|
|
draw_progressbar(i, pf_idx.album_ct, NULL);
|
|
for (j = i + 1; j < pf_idx.album_ct; j++)
|
|
{
|
|
if (idxi > 0 &&
|
|
idxi == pf_idx.album_index[j].artist_idx &&
|
|
seeki == pf_idx.album_index[j].seek)
|
|
{
|
|
pf_idx.album_index[j].artist_idx = -1;
|
|
}
|
|
else
|
|
{
|
|
i = j - 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* now fix the album list order */
|
|
rb->qsort(pf_idx.album_index, pf_idx.album_ct,
|
|
sizeof(struct album_data), compare_album_artists);
|
|
|
|
/* remove any extra untagged albums
|
|
* extra space is orphaned till restart */
|
|
for (i = 0; i < pf_idx.album_ct; i++)
|
|
{
|
|
if (pf_idx.album_index[i].artist_idx > 0)
|
|
{
|
|
if (i > 0) { i--; }
|
|
pf_idx.album_index += i;
|
|
pf_idx.album_ct -= i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
pf_idx.buf = buf;
|
|
pf_idx.buf_sz = buf_size;
|
|
pf_idx.artist_index = 0;
|
|
|
|
return (pf_idx.album_ct > 0) ? 0 : ERROR_NO_ALBUMS;
|
|
}
|
|
|
|
/*Saves the album index into a binary file to be recovered the
|
|
next time PictureFlow is launched*/
|
|
|
|
static int save_album_index(void){
|
|
int fd = rb->creat(ALBUM_INDEX,0666);
|
|
|
|
struct pf_index_t data;
|
|
memcpy(&data, &pf_idx, sizeof(struct pf_index_t));
|
|
|
|
if(fd >= 0)
|
|
{
|
|
rb->memcpy(&data.header, INDEX_HDR, sizeof(pf_idx.header));
|
|
|
|
rb->write(fd, &data, sizeof(struct pf_index_t));
|
|
|
|
rb->write(fd, data.artist_names, data.artist_len);
|
|
rb->write(fd, data.album_names, data.album_len);
|
|
|
|
rb->write(fd, data.album_index, data.album_ct * sizeof(struct album_data));
|
|
|
|
rb->close(fd);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* reads data from save file to buffer */
|
|
static inline int read2buf(int fildes, void *buf, size_t nbyte){
|
|
int read;
|
|
read = rb->read(fildes, buf, nbyte);
|
|
if (read < (int)nbyte)
|
|
return 0;
|
|
|
|
return read;
|
|
}
|
|
|
|
/*Loads the album_index information stored in the hard drive*/
|
|
static int load_album_index(void){
|
|
|
|
int i, fr = rb->open(ALBUM_INDEX, O_RDONLY);
|
|
struct pf_index_t data;
|
|
|
|
void *bufstart = pf_idx.buf;
|
|
unsigned int bufstart_sz = pf_idx.buf_sz;
|
|
|
|
void* buf = pf_idx.buf;
|
|
size_t buf_size = pf_idx.buf_sz;
|
|
|
|
unsigned int name_sz, album_idx_sz;
|
|
int album_idx, artist_idx;
|
|
|
|
if (fr >= 0){
|
|
const unsigned long filesize = rb->filesize(fr);
|
|
if (filesize > sizeof(data))
|
|
{
|
|
if (rb->read(fr, &data, sizeof(data)) == sizeof(data) &&
|
|
rb->memcmp(&(data.header), INDEX_HDR, sizeof(data.header)) == 0)
|
|
{
|
|
name_sz = data.artist_len + data.album_len;
|
|
album_idx_sz = data.album_ct * sizeof(struct album_data);
|
|
|
|
if (name_sz + album_idx_sz > bufstart_sz)
|
|
goto failure;
|
|
|
|
//rb->lseek(fr, sizeof(data) + 1, SEEK_SET);
|
|
/* artist names */
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
if (read2buf(fr, buf, data.artist_len) == 0)
|
|
goto failure;
|
|
|
|
data.artist_names = buf;
|
|
buf = (char *)buf + data.artist_len;
|
|
buf_size -= data.artist_len;
|
|
|
|
/* album names */
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
if (read2buf(fr, buf, data.album_len) == 0)
|
|
goto failure;
|
|
|
|
data.album_names = buf;
|
|
buf = (char *)buf + data.album_len;
|
|
buf_size -= data.album_len;
|
|
|
|
/* index of album names */
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
if (read2buf(fr, buf, album_idx_sz) == 0)
|
|
goto failure;
|
|
|
|
data.album_index = buf;
|
|
buf = (char *)buf + album_idx_sz;
|
|
buf_size -= album_idx_sz;
|
|
|
|
rb->close(fr);
|
|
|
|
/* sanity check loaded data */
|
|
for (i = 0; i < data.album_ct; i++)
|
|
{
|
|
album_idx = data.album_index[i].name_idx;
|
|
artist_idx = data.album_index[i].artist_idx;
|
|
if (album_idx >= (int) data.album_len ||
|
|
artist_idx >= (int) data.artist_len)
|
|
{
|
|
goto failure;
|
|
}
|
|
}
|
|
|
|
memcpy(&pf_idx, &data, sizeof(struct pf_index_t));
|
|
pf_idx.buf = buf;
|
|
pf_idx.buf_sz = buf_size;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
failure:
|
|
rb->splash(HZ/2, "Failed to load index");
|
|
if (fr >= 0)
|
|
rb->close(fr);
|
|
|
|
pf_idx.buf = bufstart;
|
|
pf_idx.buf_sz = bufstart_sz;
|
|
pf_idx.artist_ct = 0;
|
|
pf_idx.album_ct = 0;
|
|
return -1;
|
|
|
|
}
|
|
|
|
/**
|
|
Return a pointer to the album name of the given slide_index
|
|
*/
|
|
static char* get_album_name(const int slide_index)
|
|
{
|
|
char *name = pf_idx.album_names + pf_idx.album_index[slide_index].name_idx;
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
Return a pointer to the album name of the given slide_index
|
|
*/
|
|
static char* get_album_name_idx(const int slide_index, int *idx)
|
|
{
|
|
*idx = pf_idx.album_index[slide_index].name_idx;
|
|
char *name = pf_idx.album_names + pf_idx.album_index[slide_index].name_idx;
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
Return a pointer to the album artist of the given slide_index
|
|
*/
|
|
static char* get_album_artist(const int slide_index)
|
|
{
|
|
if (slide_index < pf_idx.album_ct && slide_index >= 0){
|
|
int idx = pf_idx.album_index[slide_index].artist_idx;
|
|
if (idx >= 0 && idx < (int) pf_idx.artist_len) {
|
|
char *name = pf_idx.artist_names + idx;
|
|
return name;
|
|
}
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
|
|
/**
|
|
Return a pointer to the track name of the active album
|
|
create_track_index has to be called first.
|
|
*/
|
|
static char* get_track_name(const int track_index)
|
|
{
|
|
if (track_index >= 0 && track_index < pf_tracks.count )
|
|
return pf_tracks.names + pf_tracks.index[track_index].name_idx;
|
|
return 0;
|
|
}
|
|
#if PF_PLAYBACK_CAPABLE
|
|
static char* get_track_filename(const int track_index)
|
|
{
|
|
if ( track_index < pf_tracks.count )
|
|
return pf_tracks.names + pf_tracks.index[track_index].filename_idx;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int get_wps_current_index(void)
|
|
{
|
|
char* current_artist = UNTAGGED;
|
|
char* current_album = UNTAGGED;
|
|
struct mp3entry *id3 = rb->audio_current_track();
|
|
|
|
if(id3)
|
|
{
|
|
/* we could be looking for the artist in either field */
|
|
if(id3->albumartist)
|
|
current_artist = id3->albumartist;
|
|
else if(id3->artist)
|
|
current_artist = id3->artist;
|
|
|
|
if (id3->album && rb->strlen(id3->album) > 0)
|
|
current_album = id3->album;
|
|
|
|
//rb->splashf(1000, "%s, %s", current_album, current_artist);
|
|
|
|
int i;
|
|
int album_idx, artist_idx;
|
|
|
|
for (i = 0; i < pf_idx.album_ct; i++ )
|
|
{
|
|
album_idx = pf_idx.album_index[i].name_idx;
|
|
artist_idx = pf_idx.album_index[i].artist_idx;
|
|
|
|
if(!rb->strcmp(pf_idx.album_names + album_idx, current_album) &&
|
|
!rb->strcmp(pf_idx.artist_names + artist_idx, current_artist))
|
|
return i;
|
|
}
|
|
|
|
}
|
|
rb->splash(HZ/2, "Album Not Found!");
|
|
return pf_cfg.last_album;
|
|
}
|
|
|
|
/**
|
|
Compare two unsigned ints passed via pointers.
|
|
*/
|
|
static int compare_tracks (const void *a_v, const void *b_v)
|
|
{
|
|
uint32_t a = ((struct track_data *)a_v)->sort;
|
|
uint32_t b = ((struct track_data *)b_v)->sort;
|
|
return (int)(a - b);
|
|
}
|
|
|
|
/**
|
|
Create the track index of the given slide_index.
|
|
*/
|
|
static void create_track_index(const int slide_index)
|
|
{
|
|
char temp[MAX_PATH + 1];
|
|
if ( slide_index == pf_tracks.cur_idx )
|
|
return;
|
|
|
|
if (!rb->tagcache_search(&tcs, tag_title))
|
|
goto fail;
|
|
|
|
rb->tagcache_search_add_filter(&tcs, tag_album,
|
|
pf_idx.album_index[slide_index].seek);
|
|
|
|
if (pf_idx.album_index[slide_index].artist_idx >= 0)
|
|
{
|
|
rb->tagcache_search_add_filter(&tcs, tag_albumartist,
|
|
pf_idx.album_index[slide_index].artist_seek);
|
|
}
|
|
|
|
int string_index = 0, track_num;
|
|
int disc_num;
|
|
|
|
char* result = NULL;
|
|
size_t out = 0;
|
|
|
|
pf_tracks.count = 0;
|
|
pf_tracks.names = rb->buflib_buffer_out(&buf_ctx, &out);
|
|
pf_tracks.borrowed += out;
|
|
int avail = pf_tracks.borrowed;
|
|
pf_tracks.index = (struct track_data*)(pf_tracks.names + pf_tracks.borrowed);
|
|
while (rb->tagcache_get_next(&tcs))
|
|
{
|
|
result = NULL;
|
|
if (rb->strcmp(UNTAGGED, tcs.result) == 0)
|
|
{
|
|
/* show filename instead of <untaggged> */
|
|
if (!rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
|
|
temp, sizeof(temp) - 1))
|
|
{
|
|
goto fail;
|
|
}
|
|
result = temp;
|
|
}
|
|
|
|
int len = 0, fn_idx = 0;
|
|
|
|
avail -= sizeof(struct track_data);
|
|
track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber);
|
|
disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
|
|
|
|
if (result)
|
|
{
|
|
/* if filename remove the '/' */
|
|
result = rb->strrchr(result, PATH_SEPCH);
|
|
if (result)
|
|
result++;
|
|
}
|
|
|
|
if (!result)
|
|
result = tcs.result;
|
|
|
|
if (disc_num < 0)
|
|
disc_num = 0;
|
|
retry:
|
|
if (track_num > 0)
|
|
{
|
|
if (disc_num)
|
|
fn_idx = 1 + rb->snprintf(pf_tracks.names + string_index, avail,
|
|
"%d.%02d: %s", disc_num, track_num, result);
|
|
else
|
|
fn_idx = 1 + rb->snprintf(pf_tracks.names + string_index, avail,
|
|
"%d: %s", track_num, result);
|
|
}
|
|
else
|
|
{
|
|
track_num = 0;
|
|
fn_idx = 1 + rb->snprintf(pf_tracks.names + string_index, avail,
|
|
"%s", result);
|
|
}
|
|
if (fn_idx <= 0)
|
|
goto fail;
|
|
#if PF_PLAYBACK_CAPABLE
|
|
int remain = avail - fn_idx;
|
|
if (remain >= MAX_PATH)
|
|
{ /* retrieve filename for building the playlist */
|
|
rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
|
|
pf_tracks.names + string_index + fn_idx, remain);
|
|
|
|
len = fn_idx + rb->strlen(pf_tracks.names + string_index + fn_idx) + 1;
|
|
/* make sure track name and file name are really split by a \0, else
|
|
* get_track_name might fail */
|
|
*(pf_tracks.names + string_index + fn_idx -1) = '\0';
|
|
|
|
}
|
|
else /* request more buffer so that track and filename fit */
|
|
len = (avail - remain) + MAX_PATH;
|
|
#else
|
|
len = fn_idx;
|
|
#endif
|
|
if (len > avail)
|
|
{
|
|
while (len > avail)
|
|
{
|
|
if (!free_slide_prio(0))
|
|
goto fail;
|
|
out = 0;
|
|
rb->buflib_buffer_out(&buf_ctx, &out);
|
|
avail += out;
|
|
pf_tracks.borrowed += out;
|
|
|
|
struct track_data *new_tracks;
|
|
new_tracks = (struct track_data *)(out + (uintptr_t)pf_tracks.index);
|
|
|
|
unsigned int bytes = pf_tracks.count * sizeof(struct track_data);
|
|
if (pf_tracks.count)
|
|
rb->memmove(new_tracks, pf_tracks.index, bytes);
|
|
pf_tracks.index = new_tracks;
|
|
}
|
|
goto retry;
|
|
}
|
|
|
|
avail -= len;
|
|
pf_tracks.index--;
|
|
pf_tracks.index->sort = (disc_num << 24) + (track_num << 14);
|
|
pf_tracks.index->sort += pf_tracks.count;
|
|
pf_tracks.index->name_idx = string_index;
|
|
pf_tracks.index->seek = tcs.result_seek;
|
|
#if PF_PLAYBACK_CAPABLE
|
|
pf_tracks.index->filename_idx = fn_idx + string_index;
|
|
#endif
|
|
pf_tracks.count++;
|
|
string_index += len;
|
|
}
|
|
|
|
rb->tagcache_search_finish(&tcs);
|
|
|
|
/* now fix the track list order */
|
|
rb->qsort(pf_tracks.index, pf_tracks.count,
|
|
sizeof(struct track_data), compare_tracks);
|
|
|
|
pf_tracks.cur_idx = slide_index;
|
|
return;
|
|
fail:
|
|
rb->tagcache_search_finish(&tcs);
|
|
pf_tracks.count = 0;
|
|
return;
|
|
}
|
|
|
|
/**
|
|
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
|
|
find_albumart to find the filename.
|
|
*/
|
|
static bool get_albumart_for_index_from_db(const int slide_index, char *buf,
|
|
int buflen)
|
|
{
|
|
if ( slide_index == -1 )
|
|
{
|
|
rb->strlcpy( buf, EMPTY_SLIDE, buflen );
|
|
}
|
|
|
|
if (tcs.valid || !rb->tagcache_search(&tcs, tag_filename))
|
|
return false;
|
|
|
|
bool result;
|
|
/* find the first track of the album */
|
|
rb->tagcache_search_add_filter(&tcs, tag_album,
|
|
pf_idx.album_index[slide_index].seek);
|
|
|
|
rb->tagcache_search_add_filter(&tcs, tag_albumartist,
|
|
pf_idx.album_index[slide_index].artist_seek);
|
|
|
|
if ( rb->tagcache_get_next(&tcs) ) {
|
|
struct mp3entry id3;
|
|
int fd;
|
|
|
|
#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
|
|
if (rb->tagcache_fill_tags(&id3, tcs.result))
|
|
{
|
|
rb->strlcpy(id3.path, tcs.result, sizeof(id3.path));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
fd = rb->open(tcs.result, O_RDONLY);
|
|
if (fd) {
|
|
rb->get_metadata(&id3, fd, tcs.result);
|
|
rb->close(fd);
|
|
}
|
|
}
|
|
|
|
if ( search_albumart_files(&id3, ":", buf, buflen) )
|
|
result = true;
|
|
else
|
|
result = false;
|
|
}
|
|
else {
|
|
/* did not find a matching track */
|
|
result = false;
|
|
}
|
|
rb->tagcache_search_finish(&tcs);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
Draw the PictureFlow logo
|
|
*/
|
|
static void draw_splashscreen(unsigned char * buf_tmp, size_t buf_tmp_size)
|
|
{
|
|
struct screen* display = rb->screens[SCREEN_MAIN];
|
|
#if FB_DATA_SZ > 1
|
|
ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
|
|
#endif
|
|
struct bitmap logo = {
|
|
#if LCD_WIDTH < 200
|
|
.width = 100,
|
|
.height = 18,
|
|
#else
|
|
.width = 193,
|
|
.height = 34,
|
|
#endif
|
|
.data = buf_tmp
|
|
};
|
|
int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size,
|
|
FORMAT_NATIVE, NULL);
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_background(N_BRIGHT(0));
|
|
rb->lcd_set_foreground(N_BRIGHT(255));
|
|
#else
|
|
rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
|
|
#endif
|
|
rb->lcd_clear_display();
|
|
|
|
if (ret > 0)
|
|
{
|
|
#if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
|
|
rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
|
|
#endif
|
|
display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
|
|
logo.width, logo.height);
|
|
#if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
|
|
rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
|
|
#endif
|
|
}
|
|
|
|
rb->lcd_update();
|
|
}
|
|
|
|
|
|
/**
|
|
Draw a simple progress bar
|
|
*/
|
|
static void draw_progressbar(int step, int count, char *msg)
|
|
{
|
|
static int txt_w, txt_h;
|
|
const int bar_height = 22;
|
|
const int w = LCD_WIDTH - 20;
|
|
const int x = 10;
|
|
static int y;
|
|
if (msg != NULL)
|
|
{
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_background(N_BRIGHT(0));
|
|
rb->lcd_set_foreground(N_BRIGHT(255));
|
|
#else
|
|
rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
|
|
#endif
|
|
rb->lcd_getstringsize(msg, &txt_w, &txt_h);
|
|
|
|
y = (LCD_HEIGHT - txt_h)/2;
|
|
|
|
rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, msg);
|
|
y += (txt_h + 5);
|
|
}
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_foreground(N_BRIGHT(100));
|
|
#endif
|
|
rb->lcd_drawrect(x, y, w+2, bar_height);
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_foreground(N_PIX(165, 231, 82));
|
|
#endif
|
|
|
|
rb->lcd_fillrect(x+1, y+1, step * w / count, bar_height-2);
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_foreground(N_BRIGHT(255));
|
|
#endif
|
|
rb->lcd_update();
|
|
rb->yield();
|
|
}
|
|
|
|
/* Calculate modified FNV hash of string
|
|
* has good avalanche behaviour and uniform distribution
|
|
* see http://home.comcast.net/~bretm/hash/ */
|
|
static unsigned int mfnv(char *str)
|
|
{
|
|
const unsigned int p = 16777619;
|
|
unsigned int hash = 0x811C9DC5; // 2166136261;
|
|
|
|
while(*str)
|
|
hash = (hash ^ *str++) * p;
|
|
hash += hash << 13;
|
|
hash ^= hash >> 7;
|
|
hash += hash << 3;
|
|
hash ^= hash >> 17;
|
|
hash += hash << 5;
|
|
return hash;
|
|
}
|
|
|
|
/**
|
|
Save the given bitmap as filename in the pfraw format
|
|
*/
|
|
static bool save_pfraw(char* filename, struct bitmap *bm)
|
|
{
|
|
struct pfraw_header bmph;
|
|
bmph.width = bm->width;
|
|
bmph.height = bm->height;
|
|
int fh = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
|
|
if( fh < 0 ) return false;
|
|
rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
|
|
pix_t *data = (pix_t*)( bm->data );
|
|
int y;
|
|
for( y = 0; y < bm->height; y++ )
|
|
{
|
|
rb->write( fh, data , sizeof( pix_t ) * bm->width );
|
|
data += bm->width;
|
|
}
|
|
rb->close( fh );
|
|
return true;
|
|
}
|
|
|
|
static bool incremental_albumart_cache(bool verbose)
|
|
{
|
|
if (!aa_cache.buf)
|
|
goto aa_failure;
|
|
|
|
if (aa_cache.inspected >= pf_idx.album_ct)
|
|
return false;
|
|
|
|
int idx, ret;
|
|
unsigned int hash_artist, hash_album;
|
|
unsigned int format = FORMAT_NATIVE;
|
|
|
|
bool update = (pf_cfg.cache_version == CACHE_UPDATE);
|
|
|
|
if (pf_cfg.resize)
|
|
format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
|
|
|
|
idx = aa_cache.idx;
|
|
if (idx >= pf_idx.album_ct || idx < 0) { idx = 0; } /* Rollover */
|
|
|
|
|
|
aa_cache.idx++;
|
|
aa_cache.inspected++;
|
|
if (aa_cache.idx >= pf_idx.album_ct) { aa_cache.idx = 0; } /* Rollover */
|
|
|
|
if (!get_albumart_for_index_from_db(idx, aa_cache.file, sizeof(aa_cache.file)))
|
|
goto aa_failure; //rb->strcpy(aa_cache.file, EMPTY_SLIDE_BMP);
|
|
|
|
hash_artist = mfnv(get_album_artist(idx));
|
|
hash_album = mfnv(get_album_name(idx));
|
|
|
|
rb->snprintf(aa_cache.pfraw_file, sizeof(aa_cache.pfraw_file),
|
|
CACHE_PREFIX "/%x%x.pfraw", hash_album, hash_artist);
|
|
|
|
if(rb->file_exists(aa_cache.pfraw_file)) {
|
|
if(update) {
|
|
aa_cache.slides++;
|
|
goto aa_success;
|
|
}
|
|
}
|
|
|
|
aa_cache.input_bmp.data = aa_cache.buf;
|
|
aa_cache.input_bmp.width = DISPLAY_WIDTH;
|
|
aa_cache.input_bmp.height = DISPLAY_HEIGHT;
|
|
|
|
ret = read_image_file(aa_cache.file, &aa_cache.input_bmp,
|
|
aa_cache.buf_sz, format, &format_transposed);
|
|
if (ret <= 0) {
|
|
if (verbose) {
|
|
rb->splashf(HZ, "Album art is bad: %s", get_album_name(idx));
|
|
}
|
|
|
|
goto aa_failure;
|
|
}
|
|
rb->remove(aa_cache.pfraw_file);
|
|
|
|
if (!save_pfraw(aa_cache.pfraw_file, &aa_cache.input_bmp))
|
|
{
|
|
if (verbose) { rb->splash(HZ, "Could not write bmp"); }
|
|
goto aa_failure;
|
|
}
|
|
aa_cache.slides++;
|
|
|
|
aa_failure:
|
|
if (verbose)
|
|
return false;
|
|
|
|
aa_success:
|
|
if (aa_cache.inspected >= pf_idx.album_ct)
|
|
free_all_slide_prio(0);
|
|
|
|
if(verbose)/* direct interaction with user */
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Precomupte the album art images and store them in CACHE_PREFIX.
|
|
Use the "?" bitmap if image is not found.
|
|
*/
|
|
static bool create_albumart_cache(void)
|
|
{
|
|
draw_splashscreen(pf_idx.buf, pf_idx.buf_sz);
|
|
draw_progressbar(0, pf_idx.album_ct, "Preparing artwork");
|
|
for (int i=0; i < pf_idx.album_ct; i++)
|
|
{
|
|
incremental_albumart_cache(true);
|
|
draw_progressbar(aa_cache.inspected, pf_idx.album_ct, NULL);
|
|
if (rb->button_get(false) > BUTTON_NONE)
|
|
return true;
|
|
}
|
|
if ( aa_cache.slides == 0 ) {
|
|
/* Warn the user that we couldn't find any albumart */
|
|
rb->splash(2*HZ, ID2P(LANG_NO_ALBUMART_FOUND));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Create the "?" slide, that is shown while loading
|
|
or when no cover was found.
|
|
*/
|
|
static int create_empty_slide(bool force)
|
|
{
|
|
const unsigned int format = FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
|
|
|
|
if (!aa_cache.buf)
|
|
return false;
|
|
|
|
if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
|
|
aa_cache.input_bmp.width = DISPLAY_WIDTH;
|
|
aa_cache.input_bmp.height = DISPLAY_HEIGHT;
|
|
#if LCD_DEPTH > 1
|
|
aa_cache.input_bmp.format = FORMAT_NATIVE;
|
|
#endif
|
|
aa_cache.input_bmp.data = (char*)aa_cache.buf;
|
|
|
|
scaled_read_bmp_file(EMPTY_SLIDE_BMP, &aa_cache.input_bmp,
|
|
aa_cache.buf_sz, format, &format_transposed);
|
|
|
|
if (!save_pfraw(EMPTY_SLIDE, &aa_cache.input_bmp))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Thread used for loading and preparing bitmaps in the background
|
|
*/
|
|
static void thread(void)
|
|
{
|
|
long sleep_time = 5 * HZ;
|
|
struct queue_event ev;
|
|
while (1) {
|
|
rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
|
|
switch (ev.id) {
|
|
case EV_EXIT:
|
|
return;
|
|
case EV_WAKEUP:
|
|
/* we just woke up */
|
|
break;
|
|
}
|
|
if(ev.id != SYS_TIMEOUT)
|
|
while ( load_new_slide() ) {
|
|
rb->yield();
|
|
switch (ev.id) {
|
|
case EV_EXIT:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
End the thread by posting the EV_EXIT event
|
|
*/
|
|
static void end_pf_thread(void)
|
|
{
|
|
if ( thread_is_running ) {
|
|
rb->queue_post(&thread_q, EV_EXIT, 0);
|
|
rb->thread_wait(thread_id);
|
|
/* remove the thread's queue from the broadcast list */
|
|
rb->queue_delete(&thread_q);
|
|
thread_is_running = false;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Create the thread an setup the event queue
|
|
*/
|
|
static bool create_pf_thread(void)
|
|
{
|
|
/* put the thread's queue in the bcast list */
|
|
rb->queue_init(&thread_q, true);
|
|
if ((thread_id = rb->create_thread(
|
|
thread,
|
|
thread_stack,
|
|
sizeof(thread_stack),
|
|
0,
|
|
"Picture load thread"
|
|
IF_PRIO(, PRIORITY_BUFFERING)
|
|
IF_COP(, CPU)
|
|
)
|
|
) == 0) {
|
|
return false;
|
|
}
|
|
thread_is_running = true;
|
|
rb->queue_post(&thread_q, EV_WAKEUP, 0);
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* The following functions implement the linked-list-in-array used to manage
|
|
* the LRU cache of slides, and the list of free cache slots.
|
|
*/
|
|
|
|
#define _SEEK_RIGHT_WHILE(start, cond) \
|
|
({ \
|
|
int ind_, next_ = (start); \
|
|
do { \
|
|
ind_ = next_; \
|
|
next_ = pf_sldcache.cache[ind_].next; \
|
|
} while (next_ != pf_sldcache.used && (cond)); \
|
|
ind_; \
|
|
})
|
|
|
|
#define _SEEK_LEFT_WHILE(start, cond) \
|
|
({ \
|
|
int ind_, next_ = (start); \
|
|
do { \
|
|
ind_ = next_; \
|
|
next_ = pf_sldcache.cache[ind_].prev; \
|
|
} while (ind_ != pf_sldcache.used && (cond)); \
|
|
ind_; \
|
|
})
|
|
|
|
/**
|
|
Pop the given item from the linked list starting at *head, returning the next
|
|
item, or -1 if the list is now empty.
|
|
*/
|
|
static inline int lla_pop_item (int *head, int i)
|
|
{
|
|
int prev = pf_sldcache.cache[i].prev;
|
|
int next = pf_sldcache.cache[i].next;
|
|
if (i == next)
|
|
{
|
|
*head = -1;
|
|
return -1;
|
|
}
|
|
else if (i == *head)
|
|
*head = next;
|
|
pf_sldcache.cache[next].prev = prev;
|
|
pf_sldcache.cache[prev].next = next;
|
|
return next;
|
|
}
|
|
|
|
|
|
/**
|
|
Pop the head item from the list starting at *head, returning the index of the
|
|
item, or -1 if the list is already empty.
|
|
*/
|
|
static inline int lla_pop_head (int *head)
|
|
{
|
|
int i = *head;
|
|
if (i != -1)
|
|
lla_pop_item(head, i);
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
Insert the item at index i before the one at index p.
|
|
*/
|
|
static inline void lla_insert (int i, int p)
|
|
{
|
|
int next = p;
|
|
int prev = pf_sldcache.cache[next].prev;
|
|
pf_sldcache.cache[next].prev = i;
|
|
pf_sldcache.cache[prev].next = i;
|
|
pf_sldcache.cache[i].next = next;
|
|
pf_sldcache.cache[i].prev = prev;
|
|
}
|
|
|
|
|
|
/**
|
|
Insert the item at index i at the end of the list starting at *head.
|
|
*/
|
|
static inline void lla_insert_tail (int *head, int i)
|
|
{
|
|
if (*head == -1)
|
|
{
|
|
*head = i;
|
|
pf_sldcache.cache[i].next = i;
|
|
pf_sldcache.cache[i].prev = i;
|
|
} else
|
|
lla_insert(i, *head);
|
|
}
|
|
|
|
/**
|
|
Insert the item at index i before the one at index p.
|
|
*/
|
|
static inline void lla_insert_after(int i, int p)
|
|
{
|
|
p = pf_sldcache.cache[p].next;
|
|
lla_insert(i, p);
|
|
}
|
|
|
|
|
|
/**
|
|
Insert the item at index i before the one at index p in the list starting at
|
|
*head
|
|
*/
|
|
static inline void lla_insert_before(int *head, int i, int p)
|
|
{
|
|
lla_insert(i, p);
|
|
if (*head == p)
|
|
*head = i;
|
|
}
|
|
|
|
|
|
/**
|
|
Free the used slide at index i, and its buffer, and move it to the free
|
|
slides list.
|
|
*/
|
|
static inline void free_slide(int i)
|
|
{
|
|
if (pf_sldcache.cache[i].hid != empty_slide_hid)
|
|
rb->buflib_free(&buf_ctx, pf_sldcache.cache[i].hid);
|
|
pf_sldcache.cache[i].index = -1;
|
|
lla_pop_item(&pf_sldcache.used, i);
|
|
lla_insert_tail(&pf_sldcache.free, i);
|
|
if (pf_sldcache.used == -1)
|
|
{
|
|
pf_sldcache.right_idx = -1;
|
|
pf_sldcache.left_idx = -1;
|
|
pf_sldcache.center_idx = -1;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Free one slide ranked above the given priority. If no such slide can be found,
|
|
return false.
|
|
*/
|
|
static bool free_slide_prio(int prio)
|
|
{
|
|
if (pf_sldcache.used == -1)
|
|
return false;
|
|
|
|
int i, prio_max;
|
|
int l = pf_sldcache.used;
|
|
int r = pf_sldcache.cache[pf_sldcache.used].prev;
|
|
|
|
int prio_l = pf_sldcache.cache[l].index < center_index ?
|
|
center_index - pf_sldcache.cache[l].index : 0;
|
|
int prio_r = pf_sldcache.cache[r].index > center_index ?
|
|
pf_sldcache.cache[r].index - center_index : 0;
|
|
if (prio_l > prio_r)
|
|
{
|
|
i = l;
|
|
prio_max = prio_l;
|
|
} else {
|
|
i = r;
|
|
prio_max = prio_r;
|
|
}
|
|
if (prio_max > prio)
|
|
{
|
|
if (i == pf_sldcache.left_idx)
|
|
pf_sldcache.left_idx = pf_sldcache.cache[i].next;
|
|
if (i == pf_sldcache.right_idx)
|
|
pf_sldcache.right_idx = pf_sldcache.cache[i].prev;
|
|
free_slide(i);
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Free all slides ranked above the given priority.
|
|
*/
|
|
static void free_all_slide_prio(int prio)
|
|
{
|
|
while (free_slide_prio(prio))
|
|
;;
|
|
}
|
|
|
|
|
|
/**
|
|
Read the pfraw image given as filename and return the hid of the buffer
|
|
*/
|
|
static int read_pfraw(char* filename, int prio)
|
|
{
|
|
struct pfraw_header bmph;
|
|
int fh = rb->open(filename, O_RDONLY);
|
|
if( fh < 0 ) {
|
|
/* pf_cfg.cache_version = CACHE_UPDATE; -- don't invalidate on missing pfraw */
|
|
return empty_slide_hid;
|
|
}
|
|
else
|
|
rb->read(fh, &bmph, sizeof(struct pfraw_header));
|
|
|
|
int size = sizeof(struct dim) +
|
|
sizeof( pix_t ) * bmph.width * bmph.height;
|
|
|
|
int hid;
|
|
do {
|
|
hid = rb->buflib_alloc(&buf_ctx, size);
|
|
} while (hid < 0 && free_slide_prio(prio));
|
|
|
|
if (hid < 0) {
|
|
rb->close( fh );
|
|
return 0;
|
|
}
|
|
|
|
rb->yield(); /* allow audio to play when fast scrolling */
|
|
struct dim *bm = rb->buflib_get_data(&buf_ctx, hid);
|
|
|
|
bm->width = bmph.width;
|
|
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->close( fh );
|
|
return hid;
|
|
}
|
|
|
|
|
|
/**
|
|
Load the surface for the given slide_index into the cache at cache_index.
|
|
*/
|
|
static inline bool load_and_prepare_surface(const int slide_index,
|
|
const int cache_index,
|
|
const int prio)
|
|
{
|
|
char pfraw_file[MAX_PATH];
|
|
unsigned int hash_artist = mfnv(get_album_artist(slide_index));
|
|
unsigned int hash_album = mfnv(get_album_name(slide_index));
|
|
|
|
rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%x%x.pfraw",
|
|
hash_album, hash_artist);
|
|
|
|
int hid = read_pfraw(pfraw_file, prio);
|
|
if (!hid)
|
|
return false;
|
|
|
|
pf_sldcache.cache[cache_index].hid = hid;
|
|
|
|
if ( cache_index < SLIDE_CACHE_SIZE ) {
|
|
pf_sldcache.cache[cache_index].index = slide_index;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
Load the "next" slide that we can load, freeing old slides if needed, provided
|
|
that they are further from center_index than the current slide
|
|
*/
|
|
bool load_new_slide(void)
|
|
{
|
|
int i = -1;
|
|
|
|
if (pf_sldcache.center_idx != -1)
|
|
{
|
|
int next, prev;
|
|
if (pf_sldcache.cache[pf_sldcache.center_idx].index != center_index)
|
|
{
|
|
if (pf_sldcache.cache[pf_sldcache.center_idx].index < center_index)
|
|
{
|
|
pf_sldcache.center_idx = _SEEK_RIGHT_WHILE(pf_sldcache.center_idx,
|
|
pf_sldcache.cache[next_].index <= center_index);
|
|
|
|
prev = pf_sldcache.center_idx;
|
|
next = pf_sldcache.cache[pf_sldcache.center_idx].next;
|
|
}
|
|
else
|
|
{
|
|
pf_sldcache.center_idx = _SEEK_LEFT_WHILE(pf_sldcache.center_idx,
|
|
pf_sldcache.cache[next_].index >= center_index);
|
|
|
|
next = pf_sldcache.center_idx;
|
|
prev = pf_sldcache.cache[pf_sldcache.center_idx].prev;
|
|
}
|
|
if (pf_sldcache.cache[pf_sldcache.center_idx].index != center_index)
|
|
{
|
|
if (pf_sldcache.free == -1)
|
|
free_slide_prio(0);
|
|
|
|
i = lla_pop_head(&pf_sldcache.free);
|
|
if (!load_and_prepare_surface(center_index, i, 0))
|
|
goto fail_and_refree;
|
|
|
|
if (pf_sldcache.cache[next].index == -1)
|
|
{
|
|
if (pf_sldcache.cache[prev].index == -1)
|
|
goto insert_first_slide;
|
|
else
|
|
next = pf_sldcache.cache[prev].next;
|
|
}
|
|
lla_insert(i, next);
|
|
if (pf_sldcache.cache[i].index < pf_sldcache.cache[pf_sldcache.used].index)
|
|
pf_sldcache.used = i;
|
|
|
|
pf_sldcache.center_idx = i;
|
|
pf_sldcache.left_idx = i;
|
|
pf_sldcache.right_idx = i;
|
|
return true;
|
|
}
|
|
}
|
|
int left, center, right;
|
|
left = pf_sldcache.cache[pf_sldcache.left_idx].index;
|
|
center = pf_sldcache.cache[pf_sldcache.center_idx].index;
|
|
right = pf_sldcache.cache[pf_sldcache.right_idx].index;
|
|
|
|
if (left > center)
|
|
pf_sldcache.left_idx = pf_sldcache.center_idx;
|
|
if (right < center)
|
|
pf_sldcache.right_idx = pf_sldcache.center_idx;
|
|
|
|
pf_sldcache.left_idx = _SEEK_LEFT_WHILE(pf_sldcache.left_idx,
|
|
pf_sldcache.cache[ind_].index - 1 == pf_sldcache.cache[next_].index);
|
|
|
|
pf_sldcache.right_idx = _SEEK_RIGHT_WHILE(pf_sldcache.right_idx,
|
|
pf_sldcache.cache[ind_].index - 1 == pf_sldcache.cache[next_].index);
|
|
|
|
|
|
/* update indices */
|
|
left = pf_sldcache.cache[pf_sldcache.left_idx].index;
|
|
center = pf_sldcache.cache[pf_sldcache.center_idx].index;
|
|
right = pf_sldcache.cache[pf_sldcache.right_idx].index;
|
|
|
|
int prio_l = center - left + 1;
|
|
int prio_r = right - center + 1;
|
|
if ((prio_l < prio_r || right >= number_of_slides) && left > 0)
|
|
{
|
|
if (pf_sldcache.free == -1 && !free_slide_prio(prio_l))
|
|
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;
|
|
return true;
|
|
}
|
|
} else if(right < number_of_slides - 1)
|
|
{
|
|
if (pf_sldcache.free == -1 && !free_slide_prio(prio_r))
|
|
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;
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
i = lla_pop_head(&pf_sldcache.free);
|
|
if (load_and_prepare_surface(center_index, i, 0))
|
|
{
|
|
insert_first_slide:
|
|
pf_sldcache.cache[i].next = i;
|
|
pf_sldcache.cache[i].prev = i;
|
|
pf_sldcache.center_idx = i;
|
|
pf_sldcache.left_idx = i;
|
|
pf_sldcache.right_idx = i;
|
|
pf_sldcache.used = i;
|
|
return true;
|
|
}
|
|
}
|
|
fail_and_refree:
|
|
if (i != -1)
|
|
{
|
|
lla_insert_tail(&pf_sldcache.free, i);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Get a slide from the buffer
|
|
*/
|
|
static inline struct dim *get_slide(const int hid)
|
|
{
|
|
if (!hid)
|
|
return NULL;
|
|
|
|
struct dim *bmp;
|
|
|
|
bmp = rb->buflib_get_data(&buf_ctx, hid);
|
|
|
|
return bmp;
|
|
}
|
|
|
|
|
|
/**
|
|
Return the requested surface
|
|
*/
|
|
static inline struct dim *surface(const int slide_index)
|
|
{
|
|
if (slide_index < 0)
|
|
return 0;
|
|
if (slide_index >= number_of_slides)
|
|
return 0;
|
|
int i;
|
|
if ((i = pf_sldcache.used ) != -1)
|
|
{
|
|
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);
|
|
}
|
|
return get_slide(empty_slide_hid);
|
|
}
|
|
|
|
/**
|
|
adjust slides so that they are in "steady state" position
|
|
*/
|
|
static void reset_slides(void)
|
|
{
|
|
center_slide.angle = 0;
|
|
center_slide.cx = 0;
|
|
center_slide.cy = 0;
|
|
center_slide.distance = 0;
|
|
center_slide.slide_index = center_index;
|
|
|
|
int i;
|
|
for (i = 0; i < pf_cfg.num_slides; i++) {
|
|
struct slide_data *si = &left_slides[i];
|
|
si->angle = itilt;
|
|
si->cx = -(offsetX + pf_cfg.slide_spacing * i * PFREAL_ONE);
|
|
si->cy = offsetY;
|
|
si->slide_index = center_index - 1 - i;
|
|
si->distance = 0;
|
|
}
|
|
|
|
for (i = 0; i < pf_cfg.num_slides; i++) {
|
|
struct slide_data *si = &right_slides[i];
|
|
si->angle = -itilt;
|
|
si->cx = offsetX + pf_cfg.slide_spacing * i * PFREAL_ONE;
|
|
si->cy = offsetY;
|
|
si->slide_index = center_index + 1 + i;
|
|
si->distance = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Updates look-up table and other stuff necessary for the rendering.
|
|
Call this when the viewport size or slide dimension is changed.
|
|
*
|
|
* To calculate the offset that will provide the proper margin, we use the same
|
|
* projection used to render the slides. The solution for xc, the slide center,
|
|
* is:
|
|
* xp * (zo + xs * sin(r))
|
|
* xc = xp - xs * cos(r) + ───────────────────────
|
|
* z
|
|
* TODO: support moving the side slides toward or away from the camera
|
|
*/
|
|
static void recalc_offsets(void)
|
|
{
|
|
PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
|
|
PFreal zo;
|
|
PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF +
|
|
pf_cfg.center_margin * PFREAL_ONE) * pf_cfg.zoom / 100;
|
|
PFreal cosr, sinr;
|
|
|
|
itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
|
|
cosr = fcos(-itilt);
|
|
sinr = fsin(-itilt);
|
|
zo = CAM_DIST_R * 100 / pf_cfg.zoom - CAM_DIST_R +
|
|
fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
|
|
offsetX = xp - fmul(xs, cosr) + fmuln(xp,
|
|
zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
|
|
/ CAM_DIST;
|
|
offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
|
|
}
|
|
|
|
/**
|
|
Fade the given color by spreading the fb_data
|
|
to an uint, multiply and compress the result back to a fb_data.
|
|
*/
|
|
static inline pix_t fade_color(pix_t c, unsigned a)
|
|
{
|
|
#if (LCD_PIXELFORMAT == RGB565SWAPPED)
|
|
unsigned int result;
|
|
c = swap16(c);
|
|
a = (a + 2) & 0x1fc;
|
|
result = ((c & 0xf81f) * a) & 0xf81f00;
|
|
result |= ((c & 0x7e0) * a) & 0x7e000;
|
|
result >>= 8;
|
|
return swap16(result);
|
|
|
|
#elif LCD_PIXELFORMAT == RGB565
|
|
unsigned int result;
|
|
a = (a + 2) & 0x1fc;
|
|
result = ((c & 0xf81f) * a) & 0xf81f00;
|
|
result |= ((c & 0x7e0) * a) & 0x7e000;
|
|
result >>= 8;
|
|
return result;
|
|
|
|
#elif (LCD_PIXELFORMAT == RGB888 || LCD_PIXELFORMAT == XRGB8888) // FIXME: check this
|
|
unsigned int pixel = FB_UNPACK_SCALAR_LCD(c);
|
|
unsigned int result;
|
|
a = (a + 2) & 0x1fc;
|
|
result = ((pixel & 0xff00ff) * a) & 0xff00ff00;
|
|
result |= ((pixel & 0x00ff00) * a) & 0x00ff0000;
|
|
result >>= 8;
|
|
return FB_SCALARPACK(result);
|
|
|
|
#else
|
|
unsigned val = c;
|
|
return MULUQ(val, a) >> 8;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Render a single slide
|
|
* Where xc is the slide's horizontal offset from center, xs is the horizontal
|
|
* on the slide from its center, zo is the slide's depth offset from the plane
|
|
* of the display, r is the angle at which the slide is tilted, and xp is the
|
|
* point on the display corresponding to xs on the slide, the projection
|
|
* formulas are:
|
|
*
|
|
* z * (xc + xs * cos(r))
|
|
* xp = ──────────────────────
|
|
* z + zo + xs * sin(r)
|
|
*
|
|
* z * (xc - xp) - xp * zo
|
|
* xs = ────────────────────────
|
|
* xp * sin(r) - z * cos(r)
|
|
*
|
|
* We use the xp projection once, to find the left edge of the slide on the
|
|
* display. From there, we use the xs reverse projection to find the horizontal
|
|
* offset from the slide center of each column on the screen, until we reach
|
|
* the right edge of the slide, or the screen. The reverse projection can be
|
|
* optimized by saving the numerator and denominator of the fraction, which can
|
|
* then be incremented by (z + zo) and sin(r) respectively.
|
|
*/
|
|
static void render_slide(struct slide_data *slide, const int alpha)
|
|
{
|
|
struct dim *bmp = surface(slide->slide_index);
|
|
if (!bmp) {
|
|
return;
|
|
}
|
|
if (slide->angle > 255 || slide->angle < -255)
|
|
return;
|
|
pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
|
|
|
|
const int sw = bmp->width;
|
|
const int sh = bmp->height;
|
|
const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
|
|
const int w = LCD_WIDTH;
|
|
|
|
uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
|
|
|
|
if (alpha == 256) { /* opaque -> copy table */
|
|
rb->memcpy(reftab, reflect_table, sizeof(reftab));
|
|
} else { /* precalculate faded table */
|
|
int i, lalpha;
|
|
for (i = 0; i < REFLECT_HEIGHT; i++) {
|
|
lalpha = reflect_table[i];
|
|
reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
|
|
}
|
|
}
|
|
|
|
PFreal cosr = fcos(slide->angle);
|
|
PFreal sinr = fsin(slide->angle);
|
|
PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / pf_cfg.zoom
|
|
- CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
|
|
PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
|
|
PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
|
|
(CAM_DIST_R + zo + fmul(xs,sinr)));
|
|
|
|
/* Since we're finding the screen position of the left edge of the slide,
|
|
* we round up.
|
|
*/
|
|
int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
|
|
>> PFREAL_SHIFT;
|
|
xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
|
|
if (xi >= w) {
|
|
return;
|
|
}
|
|
xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
|
|
xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
|
|
xs = fdiv(xsnum, xsden);
|
|
|
|
xsnumi = -CAM_DIST_R - zo;
|
|
xsdeni = sinr;
|
|
int x;
|
|
int dy = PFREAL_ONE;
|
|
for (x = xi; x < w; x++) {
|
|
int column = (xs - slide_left) / PFREAL_ONE;
|
|
if (column >= sw)
|
|
break;
|
|
if (zo || slide->angle)
|
|
dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
|
|
|
|
const pix_t *ptr = &src[column * bmp->height];
|
|
|
|
#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
|
|
#define PIXELSTEP_Y 1
|
|
#define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)])
|
|
#else
|
|
#define PIXELSTEP_Y BUFFER_WIDTH
|
|
#define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
|
|
#endif
|
|
|
|
int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
|
|
int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
|
|
pix_t *pixel = LCDADDR(x, (LCD_HEIGHT/2)-1 );
|
|
|
|
if (alpha == 256) {
|
|
while (p >= plim) {
|
|
*pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
|
|
p -= dy;
|
|
pixel -= PIXELSTEP_Y;
|
|
}
|
|
} else {
|
|
while (p >= plim) {
|
|
*pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
|
|
p -= dy;
|
|
pixel -= PIXELSTEP_Y;
|
|
}
|
|
}
|
|
p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
|
|
plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
|
|
int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
|
|
p + (LCD_HEIGHT/2) * dy);
|
|
pixel = LCDADDR(x, (LCD_HEIGHT/2) );
|
|
|
|
if (alpha == 256) {
|
|
while (p < plim) {
|
|
*pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
|
|
p += dy;
|
|
pixel += PIXELSTEP_Y;
|
|
}
|
|
} else {
|
|
while (p < plim) {
|
|
*pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
|
|
p += dy;
|
|
pixel += PIXELSTEP_Y;
|
|
}
|
|
}
|
|
while (p < plim2) {
|
|
int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
|
|
int lalpha = reftab[ty];
|
|
*pixel = fade_color(ptr[sh - 1 - ty], lalpha);
|
|
p += dy;
|
|
pixel += PIXELSTEP_Y;
|
|
}
|
|
|
|
if (zo || slide->angle)
|
|
{
|
|
xsnum += xsnumi;
|
|
xsden += xsdeni;
|
|
xs = fdiv(xsnum, xsden);
|
|
} else
|
|
xs += PFREAL_ONE;
|
|
|
|
}
|
|
/* let the music play... */
|
|
rb->yield();
|
|
return;
|
|
}
|
|
|
|
/**
|
|
Jump to the given slide_index
|
|
*/
|
|
static inline void set_current_slide(const int slide_index)
|
|
{
|
|
int old_center_index = center_index;
|
|
step = 0;
|
|
center_index = fbound(slide_index, 0, number_of_slides - 1);
|
|
if (old_center_index != center_index)
|
|
rb->queue_post(&thread_q, EV_WAKEUP, 0);
|
|
target = center_index;
|
|
slide_frame = slide_index << 16;
|
|
reset_slides();
|
|
}
|
|
|
|
/**
|
|
Start the animation for changing slides
|
|
*/
|
|
static void start_animation(void)
|
|
{
|
|
step = (target < center_slide.slide_index) ? -1 : 1;
|
|
pf_state = pf_scrolling;
|
|
}
|
|
|
|
/**
|
|
Go to the previous slide
|
|
*/
|
|
static void show_previous_slide(void)
|
|
{
|
|
if (step == 0) {
|
|
if (center_index > 0) {
|
|
target = center_index - 1;
|
|
start_animation();
|
|
}
|
|
} else if ( step > 0 ) {
|
|
target = center_index;
|
|
step = (target <= center_slide.slide_index) ? -1 : 1;
|
|
} else {
|
|
target = fmax(0, center_index - 2);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Go to the next slide
|
|
*/
|
|
static void show_next_slide(void)
|
|
{
|
|
if (step == 0) {
|
|
if (center_index < number_of_slides - 1) {
|
|
target = center_index + 1;
|
|
start_animation();
|
|
}
|
|
} else if ( step < 0 ) {
|
|
target = center_index;
|
|
step = (target < center_slide.slide_index) ? -1 : 1;
|
|
} else {
|
|
target = fmin(center_index + 2, number_of_slides - 1);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Render the slides. Updates only the offscreen buffer.
|
|
*/
|
|
static void render_all_slides(void)
|
|
{
|
|
mylcd_set_background(G_BRIGHT(0));
|
|
/* TODO: Optimizes this by e.g. invalidating rects */
|
|
mylcd_clear_display();
|
|
|
|
int nleft = pf_cfg.num_slides;
|
|
int nright = pf_cfg.num_slides;
|
|
|
|
int alpha;
|
|
int index;
|
|
if (step == 0) {
|
|
/* no animation, boring plain rendering */
|
|
for (index = nleft - 2; index >= 0; index--) {
|
|
alpha = (index < nleft - 2) ? 256 : 128;
|
|
alpha -= extra_fade;
|
|
if (alpha > 0 )
|
|
render_slide(&left_slides[index], alpha);
|
|
}
|
|
for (index = nright - 2; index >= 0; index--) {
|
|
alpha = (index < nright - 2) ? 256 : 128;
|
|
alpha -= extra_fade;
|
|
if (alpha > 0 )
|
|
render_slide(&right_slides[index], alpha);
|
|
}
|
|
} else {
|
|
/* the first and last slide must fade in/fade out */
|
|
|
|
/* if step<0 and nleft==1, left_slides[0] is fading in */
|
|
alpha = ((step > 0) ? 0 : ((nleft == 1) ? 256 : 128)) - fade / 2;
|
|
for (index = nleft - 1; index >= 0; index--) {
|
|
if (alpha > 0)
|
|
render_slide(&left_slides[index], alpha);
|
|
alpha += 128;
|
|
if (alpha > 256) alpha = 256;
|
|
}
|
|
/* if step>0 and nright==1, right_slides[0] is fading in */
|
|
alpha = ((step > 0) ? ((nright == 1) ? 128 : 0) : -128) + fade / 2;
|
|
for (index = nright - 1; index >= 0; index--) {
|
|
if (alpha > 0)
|
|
render_slide(&right_slides[index], alpha);
|
|
alpha += 128;
|
|
if (alpha > 256) alpha = 256;
|
|
}
|
|
}
|
|
alpha = 256;
|
|
if (step != 0 && pf_cfg.num_slides <= 2) /* fading out center slide */
|
|
alpha = (step > 0) ? 256 - fade / 2 : 128 + fade / 2;
|
|
render_slide(¢er_slide, alpha);
|
|
}
|
|
|
|
|
|
/**
|
|
Updates the animation effect. Call this periodically from a timer.
|
|
*/
|
|
static void update_scroll_animation(void)
|
|
{
|
|
if (step == 0)
|
|
return;
|
|
|
|
int speed = 16384;
|
|
int i;
|
|
|
|
/* deaccelerate when approaching the target */
|
|
if (true) {
|
|
const int max = 2 * 65536;
|
|
|
|
int fi = slide_frame;
|
|
fi -= (target << 16);
|
|
if (fi < 0)
|
|
fi = -fi;
|
|
fi = fmin(fi, max);
|
|
|
|
int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
|
|
speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
|
|
}
|
|
|
|
slide_frame += speed * step;
|
|
|
|
int index = slide_frame >> 16;
|
|
int pos = slide_frame & 0xffff;
|
|
int neg = 65536 - pos;
|
|
int tick = (step < 0) ? neg : pos;
|
|
PFreal ftick = (tick * PFREAL_ONE) >> 16;
|
|
|
|
/* the leftmost and rightmost slide must fade away */
|
|
fade = pos / 256;
|
|
|
|
if (step < 0)
|
|
index++;
|
|
if (center_index != index) {
|
|
center_index = index;
|
|
rb->queue_post(&thread_q, EV_WAKEUP, 0);
|
|
slide_frame = index << 16;
|
|
center_slide.slide_index = center_index;
|
|
for (i = 0; i < pf_cfg.num_slides; i++)
|
|
left_slides[i].slide_index = center_index - 1 - i;
|
|
for (i = 0; i < pf_cfg.num_slides; i++)
|
|
right_slides[i].slide_index = center_index + 1 + i;
|
|
}
|
|
|
|
center_slide.angle = (step * tick * itilt) >> 16;
|
|
center_slide.cx = -step * fmul(offsetX, ftick);
|
|
center_slide.cy = fmul(offsetY, ftick);
|
|
|
|
if (center_index == target) {
|
|
reset_slides();
|
|
pf_state = pf_idle;
|
|
slide_frame = center_index << 16;
|
|
step = 0;
|
|
fade = 256;
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < pf_cfg.num_slides; i++) {
|
|
struct slide_data *si = &left_slides[i];
|
|
si->angle = itilt;
|
|
si->cx =
|
|
-(offsetX + pf_cfg.slide_spacing * i * PFREAL_ONE + step
|
|
* pf_cfg.slide_spacing * ftick);
|
|
si->cy = offsetY;
|
|
}
|
|
|
|
for (i = 0; i < pf_cfg.num_slides; i++) {
|
|
struct slide_data *si = &right_slides[i];
|
|
si->angle = -itilt;
|
|
si->cx =
|
|
offsetX + pf_cfg.slide_spacing * i * PFREAL_ONE - step
|
|
* pf_cfg.slide_spacing * ftick;
|
|
si->cy = offsetY;
|
|
}
|
|
|
|
if (step > 0) {
|
|
PFreal ftick = (neg * PFREAL_ONE) >> 16;
|
|
right_slides[0].angle = -(neg * itilt) >> 16;
|
|
right_slides[0].cx = fmul(offsetX, ftick);
|
|
right_slides[0].cy = fmul(offsetY, ftick);
|
|
} else {
|
|
PFreal ftick = (pos * PFREAL_ONE) >> 16;
|
|
left_slides[0].angle = (pos * itilt) >> 16;
|
|
left_slides[0].cx = -fmul(offsetX, ftick);
|
|
left_slides[0].cy = fmul(offsetY, ftick);
|
|
}
|
|
|
|
/* must change direction ? */
|
|
if (target < index)
|
|
if (step > 0)
|
|
step = -1;
|
|
if (target > index)
|
|
if (step < 0)
|
|
step = 1;
|
|
}
|
|
|
|
|
|
/**
|
|
Cleanup the plugin
|
|
*/
|
|
static void cleanup(void)
|
|
{
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(false);
|
|
#endif
|
|
end_pf_thread();
|
|
#ifdef HAVE_BACKLIGHT
|
|
/* Turn on backlight timeout (revert to settings) */
|
|
backlight_use_settings();
|
|
#endif
|
|
|
|
#ifdef USEGSLIB
|
|
grey_release();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
Shows the settings menu
|
|
*/
|
|
static int settings_menu(void)
|
|
{
|
|
int selection = 0;
|
|
bool old_val;
|
|
|
|
MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL,
|
|
ID2P(LANG_DISPLAY_FPS),
|
|
ID2P(LANG_SPACING),
|
|
ID2P(LANG_CENTRE_MARGIN),
|
|
ID2P(LANG_NUMBER_OF_SLIDES),
|
|
ID2P(LANG_ZOOM),
|
|
ID2P(LANG_SHOW_ALBUM_TITLE),
|
|
ID2P(LANG_RESIZE_COVERS),
|
|
ID2P(LANG_REBUILD_CACHE),
|
|
ID2P(LANG_UPDATE_CACHE),
|
|
ID2P(LANG_WPS_INTEGRATION),
|
|
ID2P(LANG_BACKLIGHT));
|
|
|
|
static const struct opt_items album_name_options[] = {
|
|
{ STR(LANG_HIDE_ALBUM_TITLE_NEW) },
|
|
{ STR(LANG_SHOW_AT_THE_BOTTOM_NEW) },
|
|
{ STR(LANG_SHOW_AT_THE_TOP_NEW) },
|
|
{ STR(LANG_SHOW_ALL_AT_THE_TOP) },
|
|
{ STR(LANG_SHOW_ALL_AT_THE_BOTTOM) },
|
|
};
|
|
static const struct opt_items wps_options[] = {
|
|
{ STR(LANG_OFF) },
|
|
{ STR(LANG_DIRECT) },
|
|
{ STR(LANG_VIA_TRACK_LIST) }
|
|
};
|
|
static const struct opt_items backlight_options[] = {
|
|
{ STR(LANG_ALWAYS_ON) },
|
|
{ STR(LANG_NORMAL) },
|
|
};
|
|
|
|
do {
|
|
selection=rb->do_menu(&settings_menu,&selection, NULL, false);
|
|
switch(selection) {
|
|
case 0:
|
|
rb->set_bool(rb->str(LANG_DISPLAY_FPS), &pf_cfg.show_fps);
|
|
reset_track_list();
|
|
break;
|
|
|
|
case 1:
|
|
rb->set_int(rb->str(LANG_SPACING), "", 1,
|
|
&pf_cfg.slide_spacing,
|
|
NULL, 1, 0, 100, NULL );
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
|
|
case 2:
|
|
rb->set_int(rb->str(LANG_CENTRE_MARGIN), "", 1,
|
|
&pf_cfg.center_margin,
|
|
NULL, 1, 0, 80, NULL );
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
|
|
case 3:
|
|
rb->set_int(rb->str(LANG_NUMBER_OF_SLIDES), "", 1,
|
|
&pf_cfg.num_slides, NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
|
|
case 4:
|
|
rb->set_int(rb->str(LANG_ZOOM), "", 1, &pf_cfg.zoom,
|
|
NULL, 1, 10, 300, NULL );
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
case 5:
|
|
rb->set_option(rb->str(LANG_SHOW_ALBUM_TITLE),
|
|
&pf_cfg.show_album_name, INT, album_name_options, 5, NULL);
|
|
reset_track_list();
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
case 6:
|
|
old_val = pf_cfg.resize;
|
|
rb->set_bool(rb->str(LANG_RESIZE_COVERS), &pf_cfg.resize);
|
|
if (old_val == pf_cfg.resize) /* changed? */
|
|
break;
|
|
/* fallthrough if changed, since cache needs to be rebuilt */
|
|
case 7:
|
|
pf_cfg.cache_version = CACHE_REBUILD;
|
|
rb->remove(EMPTY_SLIDE);
|
|
configfile_save(CONFIG_FILE, config,
|
|
CONFIG_NUM_ITEMS, CONFIG_VERSION);
|
|
rb->splash(HZ, ID2P(LANG_CACHE_REBUILT_NEXT_RESTART));
|
|
break;
|
|
case 8:
|
|
pf_cfg.cache_version = CACHE_UPDATE;
|
|
rb->remove(EMPTY_SLIDE);
|
|
configfile_save(CONFIG_FILE, config,
|
|
CONFIG_NUM_ITEMS, CONFIG_VERSION);
|
|
rb->splash(HZ, ID2P(LANG_CACHE_REBUILT_NEXT_RESTART));
|
|
break;
|
|
case 9:
|
|
rb->set_option(rb->str(LANG_WPS_INTEGRATION),
|
|
&pf_cfg.auto_wps, INT, wps_options, 3, NULL);
|
|
break;
|
|
case 10:
|
|
rb->set_option(rb->str(LANG_BACKLIGHT),
|
|
&pf_cfg.backlight_mode, INT, backlight_options, 2, NULL);
|
|
break;
|
|
|
|
case MENU_ATTACHED_USB:
|
|
return PLUGIN_USB_CONNECTED;
|
|
}
|
|
} while ( selection >= 0 );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Show the main menu
|
|
*/
|
|
enum {
|
|
PF_GOTO_WPS,
|
|
#if PF_PLAYBACK_CAPABLE
|
|
PF_MENU_CLEAR_PLAYLIST,
|
|
PF_MENU_PLAYBACK_CONTROL,
|
|
#endif
|
|
PF_MENU_SETTINGS,
|
|
PF_MENU_RETURN,
|
|
PF_MENU_QUIT,
|
|
};
|
|
|
|
static int main_menu(void)
|
|
{
|
|
int selection = 0;
|
|
int result;
|
|
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_foreground(N_BRIGHT(255));
|
|
#endif
|
|
|
|
MENUITEM_STRINGLIST(main_menu, "PictureFlow Main Menu", NULL,
|
|
ID2P(LANG_GOTO_WPS),
|
|
#if PF_PLAYBACK_CAPABLE
|
|
ID2P(LANG_CLEAR_PLAYLIST),
|
|
ID2P(LANG_PLAYBACK_CONTROL),
|
|
#endif
|
|
ID2P(LANG_SETTINGS),
|
|
ID2P(LANG_RETURN),
|
|
ID2P(LANG_MENU_QUIT));
|
|
while (1) {
|
|
switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
|
|
case PF_GOTO_WPS: /* WPS */
|
|
return -2;
|
|
#if PF_PLAYBACK_CAPABLE
|
|
case PF_MENU_CLEAR_PLAYLIST:
|
|
if(rb->playlist_remove_all_tracks(NULL) == 0) {
|
|
rb->playlist_create(NULL, NULL);
|
|
rb->splash(HZ*2, ID2P(LANG_PLAYLIST_CLEARED));
|
|
}
|
|
break;
|
|
case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */
|
|
playback_control(NULL);
|
|
break;
|
|
#endif
|
|
case PF_MENU_SETTINGS:
|
|
result = settings_menu();
|
|
if ( result != 0 ) return result;
|
|
break;
|
|
case PF_MENU_RETURN:
|
|
return 0;
|
|
case PF_MENU_QUIT:
|
|
return -1;
|
|
|
|
case MENU_ATTACHED_USB:
|
|
return PLUGIN_USB_CONNECTED;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Animation step for zooming into the current cover
|
|
*/
|
|
static void update_cover_in_animation(void)
|
|
{
|
|
cover_animation_keyframe++;
|
|
if( cover_animation_keyframe < 20 ) {
|
|
center_slide.distance-=5;
|
|
center_slide.angle+=1;
|
|
extra_fade += 13;
|
|
}
|
|
else if( cover_animation_keyframe < 35 ) {
|
|
center_slide.angle+=16;
|
|
}
|
|
else {
|
|
cover_animation_keyframe = 0;
|
|
pf_state = pf_show_tracks;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Animation step for zooming out the current cover
|
|
*/
|
|
static void update_cover_out_animation(void)
|
|
{
|
|
cover_animation_keyframe++;
|
|
if( cover_animation_keyframe <= 15 ) {
|
|
center_slide.angle-=16;
|
|
}
|
|
else if( cover_animation_keyframe < 35 ) {
|
|
center_slide.distance+=5;
|
|
center_slide.angle-=1;
|
|
extra_fade -= 13;
|
|
}
|
|
else {
|
|
cover_animation_keyframe = 0;
|
|
pf_state = pf_idle;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Draw a blue gradient at y with height h
|
|
*/
|
|
static inline void draw_gradient(int y, int h)
|
|
{
|
|
int r, inc, c;
|
|
inc = (100 << 8) / h;
|
|
c = 0;
|
|
pf_tracks.sel_pulse = (pf_tracks.sel_pulse+1) % 10;
|
|
int c2 = pf_tracks.sel_pulse - 5;
|
|
for (r=0; r<h; r++) {
|
|
#ifdef HAVE_LCD_COLOR
|
|
mylcd_set_foreground(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
|
|
c2+250-(c >> 8)));
|
|
#else
|
|
mylcd_set_foreground(G_BRIGHT(c2+160-(c >> 8)));
|
|
#endif
|
|
mylcd_hline(0, LCD_WIDTH, r+y);
|
|
if ( r > h/2 )
|
|
c-=inc;
|
|
else
|
|
c+=inc;
|
|
}
|
|
}
|
|
|
|
|
|
static void track_list_yh(int char_height)
|
|
{
|
|
switch (pf_cfg.show_album_name)
|
|
{
|
|
case ALBUM_NAME_HIDE:
|
|
pf_tracks.list_y = (pf_cfg.show_fps ? char_height : 0);
|
|
pf_tracks.list_h = LCD_HEIGHT - pf_tracks.list_y;
|
|
break;
|
|
case ALBUM_NAME_BOTTOM:
|
|
pf_tracks.list_y = (pf_cfg.show_fps ? char_height : 0);
|
|
pf_tracks.list_h = LCD_HEIGHT - pf_tracks.list_y - (char_height * 3);
|
|
break;
|
|
case ALBUM_AND_ARTIST_TOP:
|
|
pf_tracks.list_y = char_height * 3;
|
|
pf_tracks.list_h = LCD_HEIGHT - pf_tracks.list_y -
|
|
(pf_cfg.show_fps ? char_height : 0);
|
|
break;
|
|
case ALBUM_AND_ARTIST_BOTTOM:
|
|
pf_tracks.list_y = (pf_cfg.show_fps ? char_height : 0);
|
|
pf_tracks.list_h = LCD_HEIGHT - pf_tracks.list_y - (char_height * 3);
|
|
break;
|
|
case ALBUM_NAME_TOP:
|
|
default:
|
|
pf_tracks.list_y = char_height * 3;
|
|
pf_tracks.list_h = LCD_HEIGHT - pf_tracks.list_y -
|
|
(pf_cfg.show_fps ? char_height : 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Reset the track list after a album change
|
|
*/
|
|
void reset_track_list(void)
|
|
{
|
|
int char_height = rb->screens[SCREEN_MAIN]->getcharheight();
|
|
int total_height;
|
|
track_list_yh(char_height);
|
|
pf_tracks.list_visible =
|
|
fmin( pf_tracks.list_h/char_height , pf_tracks.count );
|
|
|
|
pf_tracks.list_start = 0;
|
|
pf_tracks.sel = 0;
|
|
pf_tracks.last_sel = -1;
|
|
|
|
/* let the tracklist start more centered
|
|
* if the screen isn't filled with tracks */
|
|
total_height = pf_tracks.count*char_height;
|
|
if (total_height < pf_tracks.list_h)
|
|
{
|
|
pf_tracks.list_y += (pf_tracks.list_h - total_height) / 2;
|
|
pf_tracks.list_h = total_height;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Display the list of tracks
|
|
*/
|
|
static void show_track_list(void)
|
|
{
|
|
mylcd_clear_display();
|
|
if ( center_slide.slide_index != pf_tracks.cur_idx ) {
|
|
create_track_index(center_slide.slide_index);
|
|
reset_track_list();
|
|
}
|
|
int titletxt_w, titletxt_x, color, titletxt_h;
|
|
titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
|
|
|
|
int titletxt_y = pf_tracks.list_y;
|
|
int track_i;
|
|
int fade;
|
|
|
|
track_i = pf_tracks.list_start;
|
|
for (; track_i < pf_tracks.list_visible + pf_tracks.list_start; track_i++)
|
|
{
|
|
char *trackname = get_track_name(track_i);
|
|
if ( track_i == pf_tracks.sel ) {
|
|
if (pf_tracks.sel != pf_tracks.last_sel) {
|
|
set_scroll_line(trackname, PF_SCROLL_TRACK);
|
|
pf_tracks.last_sel = pf_tracks.sel;
|
|
}
|
|
draw_gradient(titletxt_y, titletxt_h);
|
|
titletxt_x = get_scroll_line_offset(PF_SCROLL_TRACK);
|
|
color = 255;
|
|
}
|
|
else {
|
|
titletxt_w = mylcd_getstringsize(trackname, NULL, NULL);
|
|
titletxt_x = (LCD_WIDTH-titletxt_w)/2;
|
|
fade = (abs(pf_tracks.sel - track_i) * 200 / pf_tracks.count);
|
|
color = 250 - fade;
|
|
}
|
|
mylcd_set_foreground(G_BRIGHT(color));
|
|
mylcd_putsxy(titletxt_x,titletxt_y,trackname);
|
|
titletxt_y += titletxt_h;
|
|
}
|
|
}
|
|
|
|
static void select_next_track(void)
|
|
{
|
|
if ( pf_tracks.sel < pf_tracks.count - 1 ) {
|
|
pf_tracks.sel++;
|
|
if (pf_tracks.sel==(pf_tracks.list_visible+pf_tracks.list_start))
|
|
pf_tracks.list_start++;
|
|
} else {
|
|
/* Rollover */
|
|
pf_tracks.sel = 0;
|
|
pf_tracks.list_start = 0;
|
|
}
|
|
}
|
|
|
|
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 {
|
|
/* Rolllover */
|
|
pf_tracks.sel = pf_tracks.count - 1;
|
|
pf_tracks.list_start = pf_tracks.count - pf_tracks.list_visible;
|
|
}
|
|
}
|
|
|
|
#if PF_PLAYBACK_CAPABLE
|
|
/*
|
|
* Puts the current tracklist into a newly created playlist and starts playling
|
|
*/
|
|
static void start_playback(bool append)
|
|
{
|
|
static int old_playlist = -1, old_shuffle = 0;
|
|
int count = 0;
|
|
int position = pf_tracks.sel;
|
|
int shuffle = rb->global_settings->playlist_shuffle;
|
|
/* reuse existing playlist if possible
|
|
* regenerate if shuffle is on or changed, since playlist index and
|
|
* selected track are "out of sync" */
|
|
if (!shuffle && !append && center_slide.slide_index == old_playlist
|
|
&& (old_shuffle == shuffle))
|
|
{
|
|
goto play;
|
|
}
|
|
/* First, replace the current playlist with a new one */
|
|
else if (append || (rb->playlist_remove_all_tracks(NULL) == 0
|
|
&& rb->playlist_create(NULL, NULL) == 0))
|
|
{
|
|
do {
|
|
rb->yield();
|
|
if (rb->playlist_insert_track(NULL, get_track_filename(count),
|
|
PLAYLIST_INSERT_LAST, false, true) < 0)
|
|
break;
|
|
} while(++count < pf_tracks.count);
|
|
rb->playlist_sync(NULL);
|
|
}
|
|
else
|
|
return;
|
|
|
|
if (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;
|
|
old_shuffle = shuffle;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
Draw the current album name
|
|
*/
|
|
static void draw_album_text(void)
|
|
{
|
|
if (pf_cfg.show_album_name == ALBUM_NAME_HIDE)
|
|
return;
|
|
|
|
static int prev_albumtxt_index = -1;
|
|
int albumtxt_index;
|
|
int char_height;
|
|
int albumtxt_x, albumtxt_y, artisttxt_x;
|
|
int album_idx = 0;
|
|
|
|
char *albumtxt;
|
|
char *artisttxt;
|
|
int c;
|
|
/* Draw album text */
|
|
if ( pf_state == pf_scrolling ) {
|
|
c = ((slide_frame & 0xffff )/ 255);
|
|
if (step < 0) c = 255-c;
|
|
if (c > 128 ) { /* half way to next slide .. still not perfect! */
|
|
albumtxt_index = center_index+step;
|
|
c = (c-128)*2;
|
|
}
|
|
else {
|
|
albumtxt_index = center_index;
|
|
c = (128-c)*2;
|
|
}
|
|
}
|
|
else {
|
|
albumtxt_index = center_index;
|
|
c= 255;
|
|
}
|
|
albumtxt = get_album_name_idx(albumtxt_index, &album_idx);
|
|
|
|
mylcd_set_foreground(G_BRIGHT(c));
|
|
if (albumtxt_index != prev_albumtxt_index) {
|
|
set_scroll_line(albumtxt, PF_SCROLL_ALBUM);
|
|
prev_albumtxt_index = albumtxt_index;
|
|
}
|
|
|
|
char_height = rb->screens[SCREEN_MAIN]->getcharheight();
|
|
switch(pf_cfg.show_album_name){
|
|
case ALBUM_AND_ARTIST_TOP:
|
|
albumtxt_y = 0;
|
|
break;
|
|
case ALBUM_NAME_BOTTOM:
|
|
case ALBUM_AND_ARTIST_BOTTOM:
|
|
albumtxt_y = (LCD_HEIGHT - (char_height * 5 / 2));
|
|
break;
|
|
case ALBUM_NAME_TOP:
|
|
default:
|
|
albumtxt_y = char_height / 2;
|
|
break;
|
|
}
|
|
|
|
albumtxt_x = get_scroll_line_offset(PF_SCROLL_ALBUM);
|
|
|
|
|
|
if ((pf_cfg.show_album_name == ALBUM_AND_ARTIST_TOP)
|
|
|| (pf_cfg.show_album_name == ALBUM_AND_ARTIST_BOTTOM)){
|
|
|
|
if (album_idx != (int) pf_idx.album_untagged_idx)
|
|
mylcd_putsxy(albumtxt_x, albumtxt_y, albumtxt);
|
|
|
|
artisttxt = get_album_artist(albumtxt_index);
|
|
set_scroll_line(artisttxt, PF_SCROLL_ARTIST);
|
|
artisttxt_x = get_scroll_line_offset(PF_SCROLL_ARTIST);
|
|
int y_offset = char_height + char_height/2;
|
|
mylcd_putsxy(artisttxt_x, albumtxt_y + y_offset, artisttxt);
|
|
} else {
|
|
mylcd_putsxy(albumtxt_x, albumtxt_y, albumtxt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Display an error message and wait for input.
|
|
*/
|
|
static void error_wait(const char *message)
|
|
{
|
|
rb->splashf(0, "%s. Press any button to continue.", message);
|
|
while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
|
|
rb->yield();
|
|
rb->sleep(2 * HZ);
|
|
}
|
|
|
|
/**
|
|
Main function that also contain the main plasma
|
|
algorithm.
|
|
*/
|
|
static int pictureflow_main(void)
|
|
{
|
|
int ret = SUCCESS;
|
|
|
|
rb->lcd_setfont(FONT_UI);
|
|
|
|
if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
|
|
if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
|
|
error_wait("Could not create directory " CACHE_PREFIX);
|
|
return PLUGIN_ERROR;
|
|
}
|
|
}
|
|
|
|
rb->memset(&aa_cache, 0, sizeof(struct albumart_t));
|
|
config_set_defaults(&pf_cfg);
|
|
|
|
configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
|
|
if(pf_cfg.auto_wps == 0)
|
|
draw_splashscreen(pf_idx.buf, pf_idx.buf_sz);
|
|
if(pf_cfg.backlight_mode == 0) {
|
|
/* Turn off backlight timeout */
|
|
#ifdef HAVE_BACKLIGHT
|
|
backlight_ignore_timeout();
|
|
#endif
|
|
}
|
|
|
|
init_scroll_lines();
|
|
init_reflect_table();
|
|
|
|
ALIGN_BUFFER(pf_idx.buf, pf_idx.buf_sz, 4);
|
|
|
|
/*Scan will trigger when no file is found or the option was activated*/
|
|
if ((pf_cfg.cache_version != CACHE_VERSION)||(load_album_index() < 0)){
|
|
rb->splash(HZ/2,"Creating index, please wait");
|
|
ret = create_album_index();
|
|
|
|
if (ret == 0){
|
|
pf_cfg.cache_version = CACHE_REBUILD;
|
|
if (save_album_index() < 0) {
|
|
rb->splash(HZ, "Could not write index");
|
|
};
|
|
}
|
|
}
|
|
|
|
if (ret == ERROR_BUFFER_FULL) {
|
|
error_wait("Not enough memory for album names");
|
|
return PLUGIN_ERROR;
|
|
} else if (ret == ERROR_NO_ALBUMS) {
|
|
error_wait("No albums found. Please enable database");
|
|
return PLUGIN_ERROR;
|
|
} else if (ret == ERROR_USER_ABORT) {
|
|
error_wait("User aborted.");
|
|
return PLUGIN_ERROR;
|
|
}
|
|
|
|
ALIGN_BUFFER(pf_idx.buf, pf_idx.buf_sz, 4);
|
|
number_of_slides = pf_idx.album_ct;
|
|
|
|
size_t aa_bufsz = ALIGN_DOWN(pf_idx.buf_sz / 4, 0x4);
|
|
|
|
if (aa_bufsz < DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(pix_t))
|
|
{
|
|
error_wait("Not enough memory for album art cache");
|
|
return PLUGIN_ERROR;
|
|
}
|
|
|
|
pf_idx.buf_sz -= aa_bufsz;
|
|
ALIGN_BUFFER(pf_idx.buf, pf_idx.buf_sz, 4);
|
|
aa_cache.buf = (char*) pf_idx.buf + aa_bufsz;
|
|
aa_cache.buf_sz = aa_bufsz;
|
|
ALIGN_BUFFER(aa_cache.buf, aa_cache.buf_sz, 4);
|
|
|
|
if (!create_empty_slide(pf_cfg.cache_version != CACHE_VERSION)) {
|
|
config_save(CACHE_REBUILD);
|
|
error_wait("Could not load the empty slide");
|
|
return PLUGIN_ERROR;
|
|
}
|
|
|
|
if ((pf_cfg.cache_version != CACHE_VERSION) && !create_albumart_cache()) {
|
|
config_save(CACHE_REBUILD);
|
|
error_wait("Could not create album art cache");
|
|
} else if(aa_cache.inspected < pf_idx.album_ct) {
|
|
rb->splash(HZ * 2, "Updating album art cache in background");
|
|
}
|
|
|
|
if (pf_cfg.cache_version != CACHE_VERSION)
|
|
{
|
|
config_save(CACHE_VERSION);
|
|
}
|
|
|
|
rb->buflib_init(&buf_ctx, (void *)pf_idx.buf, pf_idx.buf_sz);
|
|
|
|
if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
|
|
{
|
|
error_wait("Unable to load empty slide image");
|
|
return PLUGIN_ERROR;
|
|
}
|
|
|
|
if (!create_pf_thread()) {
|
|
error_wait("Cannot create thread!");
|
|
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;
|
|
|
|
buffer = LCD_BUF;
|
|
|
|
pf_state = pf_idle;
|
|
|
|
pf_tracks.cur_idx = -1;
|
|
extra_fade = 0;
|
|
slide_frame = 0;
|
|
step = 0;
|
|
target = 0;
|
|
fade = 256;
|
|
|
|
recalc_offsets();
|
|
reset_slides();
|
|
set_current_slide(get_wps_current_index());
|
|
|
|
char fpstxt[10];
|
|
int button;
|
|
|
|
int frames = 0;
|
|
long last_update = *rb->current_tick;
|
|
long current_update;
|
|
long update_interval = 100;
|
|
int fps = 0;
|
|
int fpstxt_y;
|
|
|
|
bool instant_update;
|
|
#ifdef USEGSLIB
|
|
grey_show(true);
|
|
grey_set_drawmode(DRMODE_FG);
|
|
#endif
|
|
rb->lcd_set_drawmode(DRMODE_FG);
|
|
while (true) {
|
|
current_update = *rb->current_tick;
|
|
frames++;
|
|
|
|
/* Initial rendering */
|
|
instant_update = false;
|
|
|
|
update_scroll_lines();
|
|
|
|
/* Handle states */
|
|
switch ( pf_state ) {
|
|
case pf_scrolling:
|
|
update_scroll_animation();
|
|
render_all_slides();
|
|
instant_update = true;
|
|
break;
|
|
case pf_cover_in:
|
|
update_cover_in_animation();
|
|
render_all_slides();
|
|
instant_update = true;
|
|
break;
|
|
case pf_cover_out:
|
|
update_cover_out_animation();
|
|
render_all_slides();
|
|
instant_update = true;
|
|
break;
|
|
case pf_show_tracks:
|
|
show_track_list();
|
|
break;
|
|
case pf_idle:
|
|
render_all_slides();
|
|
incremental_albumart_cache(false);
|
|
break;
|
|
}
|
|
|
|
/* Calculate FPS */
|
|
if (current_update - last_update > update_interval) {
|
|
fps = frames * HZ / (current_update - last_update);
|
|
last_update = current_update;
|
|
frames = 0;
|
|
}
|
|
/* Draw FPS */
|
|
if (pf_cfg.show_fps)
|
|
{
|
|
#ifdef USEGSLIB
|
|
mylcd_set_foreground(G_BRIGHT(255));
|
|
#else
|
|
mylcd_set_foreground(G_PIX(255,0,0));
|
|
#endif
|
|
rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
|
|
if (pf_cfg.show_album_name == ALBUM_NAME_TOP)
|
|
fpstxt_y = LCD_HEIGHT -
|
|
rb->screens[SCREEN_MAIN]->getcharheight();
|
|
else
|
|
fpstxt_y = 0;
|
|
mylcd_putsxy(0, fpstxt_y, fpstxt);
|
|
}
|
|
draw_album_text();
|
|
|
|
|
|
/* Copy offscreen buffer to LCD and give time to other threads */
|
|
mylcd_update();
|
|
rb->yield();
|
|
|
|
/*/ Handle buttons */
|
|
button = rb->get_custom_action(CONTEXT_PLUGIN
|
|
#ifndef USE_CORE_PREVNEXT
|
|
|(pf_state == pf_show_tracks ? 1 : 0)
|
|
#endif
|
|
,instant_update ? 0 : HZ/16,
|
|
get_context_map);
|
|
|
|
switch (button) {
|
|
case PF_QUIT:
|
|
return PLUGIN_OK;
|
|
case PF_WPS:
|
|
return PLUGIN_GOTO_WPS;
|
|
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;
|
|
}
|
|
if (pf_state == pf_idle || pf_state == pf_scrolling)
|
|
return PLUGIN_OK;
|
|
break;
|
|
case PF_MENU:
|
|
#ifdef USEGSLIB
|
|
grey_show(false);
|
|
#endif
|
|
ret = main_menu();
|
|
if ( ret == -2 ) return PLUGIN_GOTO_WPS;
|
|
if ( ret == -1 ) return PLUGIN_OK;
|
|
if ( ret != 0 ) return ret;
|
|
#ifdef USEGSLIB
|
|
grey_show(true);
|
|
#endif
|
|
mylcd_set_drawmode(DRMODE_FG);
|
|
break;
|
|
|
|
case PF_NEXT:
|
|
case PF_NEXT_REPEAT:
|
|
if ( pf_state == pf_show_tracks )
|
|
select_next_track();
|
|
if ( pf_state == pf_idle || pf_state == pf_scrolling )
|
|
show_next_slide();
|
|
break;
|
|
|
|
case PF_PREV:
|
|
case PF_PREV_REPEAT:
|
|
if ( pf_state == pf_show_tracks )
|
|
select_prev_track();
|
|
if ( pf_state == pf_idle || pf_state == pf_scrolling )
|
|
show_previous_slide();
|
|
break;
|
|
#if PF_PLAYBACK_CAPABLE
|
|
case PF_CONTEXT:
|
|
if ( pf_cfg.auto_wps != 0 ) {
|
|
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));
|
|
}
|
|
else if( pf_state == pf_show_tracks ) {
|
|
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));
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
case PF_TRACKLIST:
|
|
if ( pf_cfg.auto_wps == 1 && pf_state == pf_idle ) {
|
|
pf_state = pf_cover_in;
|
|
break;
|
|
}
|
|
case PF_SELECT:
|
|
if ( pf_state == pf_idle ) {
|
|
#if PF_PLAYBACK_CAPABLE
|
|
if(pf_cfg.auto_wps == 1) {
|
|
create_track_index(center_slide.slide_index);
|
|
reset_track_list();
|
|
start_playback(false);
|
|
pf_cfg.last_album = center_index;
|
|
return PLUGIN_GOTO_WPS;
|
|
}
|
|
else
|
|
#endif
|
|
pf_state = pf_cover_in;
|
|
}
|
|
else if ( pf_state == pf_show_tracks ) {
|
|
#if PF_PLAYBACK_CAPABLE
|
|
start_playback(false);
|
|
if(pf_cfg.auto_wps != 0) {
|
|
pf_cfg.last_album = center_index;
|
|
return PLUGIN_GOTO_WPS;
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
default:
|
|
exit_on_usb(button);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*************************** Plugin entry point ****************************/
|
|
|
|
enum plugin_status plugin_start(const void *parameter)
|
|
{
|
|
struct viewport *vp_main = rb->lcd_set_viewport(NULL);
|
|
lcd_fb = vp_main->buffer->fb_ptr;
|
|
|
|
int ret;
|
|
(void) parameter;
|
|
|
|
void * buf;
|
|
size_t buf_size;
|
|
bool prompt = (parameter && (((char *) parameter)[0] == ACTIVITY_MAINMENU));
|
|
|
|
if (!check_database(prompt))
|
|
{
|
|
if (prompt)
|
|
return PLUGIN_OK;
|
|
else
|
|
error_wait("Please enable database");
|
|
|
|
return PLUGIN_ERROR;
|
|
}
|
|
|
|
atexit(cleanup);
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(true);
|
|
#endif
|
|
#if PF_PLAYBACK_CAPABLE
|
|
buf = rb->plugin_get_buffer(&buf_size);
|
|
#else
|
|
buf = rb->plugin_get_audio_buffer(&buf_size);
|
|
#ifndef SIMULATOR
|
|
if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
|
|
{
|
|
uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
|
|
buf_size = MIN(buf_size, tmp_size);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef USEGSLIB
|
|
long grey_buf_used;
|
|
if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
|
|
LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
|
|
{
|
|
error_wait("Greylib init failed!");
|
|
return PLUGIN_ERROR;
|
|
}
|
|
grey_setfont(FONT_UI);
|
|
buf_size -= grey_buf_used;
|
|
buf = (void*)(grey_buf_used + (char*)buf);
|
|
#endif
|
|
|
|
/* store buffer pointers and sizes */
|
|
pf_idx.buf = buf;
|
|
pf_idx.buf_sz = buf_size;
|
|
|
|
ret = pictureflow_main();
|
|
if ( ret == PLUGIN_OK || ret == PLUGIN_GOTO_WPS) {
|
|
if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
|
|
CONFIG_VERSION))
|
|
{
|
|
rb->splash(HZ, ID2P(LANG_ERROR_WRITING_CONFIG));
|
|
ret = PLUGIN_ERROR;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|