3b18158688
The problem r26713 addressed was not caused by missing yields but by too high priority of the background loader thread. Undoing the yield() change and decreasing the priority fixes the audio drop as well but doesn't cause jerkiness. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30000 a1c6a512-1295-4272-9138-f99709370657
2973 lines
84 KiB
C
2973 lines
84 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"
|
|
#include "lib/buflib.h"
|
|
|
|
|
|
|
|
/******************************* Globals ***********************************/
|
|
|
|
/*
|
|
* 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 == SAMSUNG_YH_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},
|
|
#else
|
|
{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},
|
|
#endif
|
|
#if CONFIG_KEYPAD == ONDIO_PAD
|
|
{PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
|
|
{PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
|
|
{ACTION_NONE, BUTTON_UP, BUTTON_NONE},
|
|
{ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
|
|
{ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
|
|
#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 == ARCHOS_AV300_PAD
|
|
{PF_QUIT, BUTTON_OFF, BUTTON_NONE},
|
|
#elif 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
|
|
{PF_QUIT, BUTTON_POWER, BUTTON_NONE},
|
|
#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)
|
|
{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 || \
|
|
CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
|
|
{PF_QUIT, BUTTON_OFF, BUTTON_NONE},
|
|
#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
|
|
{PF_QUIT, BUTTON_REC, BUTTON_NONE},
|
|
#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 rb->lcd_framebuffer
|
|
#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 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 ERROR_NO_ALBUMS -1
|
|
#define ERROR_BUFFER_FULL -2
|
|
|
|
/* current version for cover cache */
|
|
#define CACHE_VERSION 3
|
|
#define CONFIG_VERSION 1
|
|
#define CONFIG_FILE "pictureflow.cfg"
|
|
|
|
/** structs we use */
|
|
|
|
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;
|
|
long seek;
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
enum pf_scroll_line_type {
|
|
PF_SCROLL_TRACK = 0,
|
|
PF_SCROLL_ALBUM,
|
|
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
|
|
};
|
|
static char* show_album_name_conf[] =
|
|
{
|
|
"hide",
|
|
"bottom",
|
|
"top"
|
|
};
|
|
|
|
#define MAX_SPACING 40
|
|
#define MAX_MARGIN 80
|
|
|
|
/* config values and their defaults */
|
|
static int slide_spacing = DISPLAY_WIDTH / 4;
|
|
static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
|
|
static int num_slides = 4;
|
|
static int zoom = 100;
|
|
static bool show_fps = false;
|
|
static int auto_wps = 0;
|
|
static int last_album = 0;
|
|
static int backlight_mode = 0;
|
|
static bool resize = true;
|
|
static int cache_version = 0;
|
|
static int show_album_name = (LCD_HEIGHT > 100)
|
|
? ALBUM_NAME_TOP : ALBUM_NAME_BOTTOM;
|
|
|
|
static struct configdata config[] =
|
|
{
|
|
{ TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
|
|
NULL },
|
|
{ TYPE_INT, 0, MAX_MARGIN, { .int_p = ¢er_margin }, "center margin",
|
|
NULL },
|
|
{ TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
|
|
NULL },
|
|
{ TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
|
|
{ TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
|
|
{ TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
|
|
{ TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
|
|
{ TYPE_ENUM, 0, 3, { .int_p = &show_album_name }, "show album name",
|
|
show_album_name_conf },
|
|
{ TYPE_INT, 0, 2, { .int_p = &auto_wps }, "auto wps", NULL },
|
|
{ TYPE_INT, 0, 999999, { .int_p = &last_album }, "last album", NULL },
|
|
{ TYPE_INT, 0, 1, { .int_p = &backlight_mode }, "backlight", 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 slide_cache cache[SLIDE_CACHE_SIZE];
|
|
static int cache_free;
|
|
static int cache_used = -1;
|
|
static int cache_left_index = -1;
|
|
static int cache_right_index = -1;
|
|
static int cache_center_index = -1;
|
|
|
|
/* 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 album_data *album;
|
|
static char *album_names;
|
|
static int album_count;
|
|
|
|
static struct track_data *tracks;
|
|
static char *track_names;
|
|
static size_t borrowed = 0;
|
|
static int track_count;
|
|
static int track_index;
|
|
static int selected_track;
|
|
static int selected_track_pulse;
|
|
void reset_track_list(void);
|
|
|
|
void * buf;
|
|
size_t buf_size;
|
|
|
|
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];
|
|
static int prev_albumtxt_index = -1;
|
|
static int last_selected_track = -1;
|
|
|
|
static int start_index_track_list = 0;
|
|
static int track_list_visible_entries = 0;
|
|
static int track_list_y;
|
|
static int track_list_h;
|
|
|
|
/*
|
|
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);
|
|
static inline unsigned fade_color(pix_t c, unsigned a);
|
|
bool load_new_slide(void);
|
|
int load_surface(int);
|
|
|
|
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))))
|
|
|
|
#if CONFIG_CPU == SH7034
|
|
/* 16*16->32 bit multiplication is a single instrcution on the SH1 */
|
|
#define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
|
|
#else
|
|
#define MULUQ(a, b) ((a) * (b))
|
|
#endif
|
|
|
|
|
|
#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));
|
|
}
|
|
|
|
static inline unsigned scale_val(unsigned val, unsigned bits)
|
|
{
|
|
val = val * ((1 << bits) - 1);
|
|
return ((val >> 8) + val + 128) >> 8;
|
|
}
|
|
|
|
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_val(qp->red, 5);
|
|
g = scale_val(qp->green, 6);
|
|
b = scale_val((qp++)->blue, 5);
|
|
*dest = LCD_RGBPACK_LCD(r,g,b);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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_rgb *qp = (struct uint32_rgb*)row_in;
|
|
int r, g, b;
|
|
for (; dest < end; dest += ctx->bm->height)
|
|
{
|
|
r = scale_val(SC_OUT(qp->r, ctx), 5);
|
|
g = scale_val(SC_OUT(qp->g, ctx), 6);
|
|
b = scale_val(SC_OUT(qp->b, ctx), 5);
|
|
qp++;
|
|
*dest = LCD_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_rgb *qp = (struct uint32_rgb*)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_val(r, 5);
|
|
g = scale_val(g, 6);
|
|
b = scale_val(b, 5);
|
|
*dest = LCD_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 */
|
|
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);
|
|
}
|
|
|
|
/**
|
|
Create an index of all albums from the database.
|
|
Also store the album names so we can access them later.
|
|
*/
|
|
int create_album_index(void)
|
|
{
|
|
album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
|
|
rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
|
|
album_count = 0;
|
|
rb->tagcache_search(&tcs, tag_album);
|
|
unsigned int l, name_idx = 0;
|
|
album_names = buf;
|
|
while (rb->tagcache_get_next(&tcs))
|
|
{
|
|
buf_size -= sizeof(struct album_data);
|
|
l = tcs.result_len;
|
|
album[-album_count].name_idx = name_idx;
|
|
|
|
if ( l > buf_size )
|
|
/* not enough memory */
|
|
return ERROR_BUFFER_FULL;
|
|
|
|
rb->strcpy(buf, tcs.result);
|
|
buf_size -= l;
|
|
buf = l + (char *)buf;
|
|
album[-album_count].seek = tcs.result_seek;
|
|
name_idx += l;
|
|
album_count++;
|
|
}
|
|
rb->tagcache_search_finish(&tcs);
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
int i;
|
|
struct album_data* tmp_album = (struct album_data*)buf;
|
|
for (i = album_count - 1; i >= 0; i--)
|
|
tmp_album[i] = album[-i];
|
|
album = tmp_album;
|
|
buf = album + album_count;
|
|
return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
|
|
}
|
|
|
|
/**
|
|
Return a pointer to the album name of the given slide_index
|
|
*/
|
|
char* get_album_name(const int slide_index)
|
|
{
|
|
return album_names + album[slide_index].name_idx;
|
|
}
|
|
|
|
/**
|
|
Return a pointer to the track name of the active album
|
|
create_track_index has to be called first.
|
|
*/
|
|
char* get_track_name(const int track_index)
|
|
{
|
|
if ( track_index < track_count )
|
|
return track_names + tracks[track_index].name_idx;
|
|
return 0;
|
|
}
|
|
#if PF_PLAYBACK_CAPABLE
|
|
char* get_track_filename(const int track_index)
|
|
{
|
|
if ( track_index < track_count )
|
|
return track_names + tracks[track_index].filename_idx;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int get_wps_current_index(void)
|
|
{
|
|
struct mp3entry *id3 = rb->audio_current_track();
|
|
if(id3 && id3->album) {
|
|
int i;
|
|
for( i=0; i < album_count; i++ )
|
|
{
|
|
if(!rb->strcmp(album_names + album[i].name_idx, id3->album))
|
|
return i;
|
|
}
|
|
}
|
|
return last_album;
|
|
}
|
|
|
|
/**
|
|
Compare two unsigned ints passed via pointers.
|
|
*/
|
|
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.
|
|
*/
|
|
void create_track_index(const int slide_index)
|
|
{
|
|
if ( slide_index == track_index )
|
|
return;
|
|
track_index = slide_index;
|
|
|
|
if (!rb->tagcache_search(&tcs, tag_title))
|
|
goto fail;
|
|
|
|
rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
|
|
track_count=0;
|
|
int string_index = 0, track_num;
|
|
int disc_num;
|
|
size_t out = 0;
|
|
track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
|
|
borrowed += out;
|
|
int avail = borrowed;
|
|
tracks = (struct track_data*)(track_names + borrowed);
|
|
while (rb->tagcache_get_next(&tcs))
|
|
{
|
|
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 (disc_num < 0)
|
|
disc_num = 0;
|
|
retry:
|
|
if (track_num > 0)
|
|
{
|
|
if (disc_num)
|
|
fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
|
|
"%d.%02d: %s", disc_num, track_num, tcs.result);
|
|
else
|
|
fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
|
|
"%d: %s", track_num, tcs.result);
|
|
}
|
|
else
|
|
{
|
|
track_num = 0;
|
|
fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
|
|
"%s", tcs.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,
|
|
track_names + string_index + fn_idx, remain);
|
|
len = fn_idx + rb->strlen(track_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 */
|
|
*(track_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;
|
|
buflib_buffer_out(&buf_ctx, &out);
|
|
avail += out;
|
|
borrowed += out;
|
|
|
|
struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
|
|
unsigned int bytes = track_count * sizeof(struct track_data);
|
|
if (track_count)
|
|
rb->memmove(new_tracks, tracks, bytes);
|
|
tracks = new_tracks;
|
|
}
|
|
goto retry;
|
|
}
|
|
|
|
avail -= len;
|
|
tracks--;
|
|
tracks->sort = (disc_num << 24) + (track_num << 14) + track_count;
|
|
tracks->name_idx = string_index;
|
|
tracks->seek = tcs.result_seek;
|
|
#if PF_PLAYBACK_CAPABLE
|
|
tracks->filename_idx = fn_idx + string_index;
|
|
#endif
|
|
track_count++;
|
|
string_index += len;
|
|
}
|
|
|
|
rb->tagcache_search_finish(&tcs);
|
|
|
|
/* now fix the track list order */
|
|
rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
|
|
return;
|
|
fail:
|
|
track_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.
|
|
*/
|
|
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 (!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, album[slide_index].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);
|
|
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
|
|
*/
|
|
void draw_splashscreen(void)
|
|
{
|
|
unsigned char * buf_tmp = buf;
|
|
size_t buf_tmp_size = buf_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
|
|
*/
|
|
void draw_progressbar(int step)
|
|
{
|
|
int txt_w, txt_h;
|
|
const int bar_height = 22;
|
|
const int w = LCD_WIDTH - 20;
|
|
const int x = 10;
|
|
|
|
rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
|
|
|
|
int y = (LCD_HEIGHT - txt_h)/2;
|
|
|
|
rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
|
|
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 / album_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/ */
|
|
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
|
|
*/
|
|
bool save_pfraw(char* filename, struct bitmap *bm)
|
|
{
|
|
struct pfraw_header bmph;
|
|
bmph.width = bm->width;
|
|
bmph.height = bm->height;
|
|
int fh = rb->creat( filename , 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;
|
|
}
|
|
|
|
/**
|
|
Precomupte the album art images and store them in CACHE_PREFIX.
|
|
Use the "?" bitmap if image is not found.
|
|
*/
|
|
bool create_albumart_cache(void)
|
|
{
|
|
int ret;
|
|
|
|
int i, slides = 0;
|
|
struct bitmap input_bmp;
|
|
|
|
char pfraw_file[MAX_PATH];
|
|
char albumart_file[MAX_PATH];
|
|
unsigned int format = FORMAT_NATIVE;
|
|
bool update = (cache_version == CACHE_UPDATE);
|
|
if (resize)
|
|
format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
|
|
for (i=0; i < album_count; i++)
|
|
{
|
|
draw_progressbar(i);
|
|
|
|
rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%x.pfraw",
|
|
mfnv(get_album_name(i)));
|
|
/* delete existing cache, so it's a true rebuild */
|
|
if(rb->file_exists(pfraw_file)) {
|
|
if(update) {
|
|
slides++;
|
|
continue;
|
|
}
|
|
rb->remove(pfraw_file);
|
|
}
|
|
if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
|
|
rb->strcpy(albumart_file, EMPTY_SLIDE_BMP);
|
|
|
|
input_bmp.data = buf;
|
|
input_bmp.width = DISPLAY_WIDTH;
|
|
input_bmp.height = DISPLAY_HEIGHT;
|
|
ret = read_image_file(albumart_file, &input_bmp, buf_size,
|
|
format, &format_transposed);
|
|
if (ret <= 0) {
|
|
rb->splashf(HZ, "Album art is bad: %s", get_album_name(i));
|
|
rb->strcpy(albumart_file, EMPTY_SLIDE_BMP);
|
|
ret = read_image_file(albumart_file, &input_bmp, buf_size,
|
|
format, &format_transposed);
|
|
if(ret <= 0)
|
|
continue;
|
|
}
|
|
if (!save_pfraw(pfraw_file, &input_bmp))
|
|
{
|
|
rb->splash(HZ, "Could not write bmp");
|
|
continue;
|
|
}
|
|
slides++;
|
|
if ( rb->button_get(false) == PF_MENU ) return false;
|
|
}
|
|
draw_progressbar(i);
|
|
if ( slides == 0 ) {
|
|
/* Warn the user that we couldn't find any albumart */
|
|
rb->splash(2*HZ, "No album art found");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Create the "?" slide, that is shown while loading
|
|
or when no cover was found.
|
|
*/
|
|
int create_empty_slide(bool force)
|
|
{
|
|
if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
|
|
struct bitmap input_bmp;
|
|
input_bmp.width = DISPLAY_WIDTH;
|
|
input_bmp.height = DISPLAY_HEIGHT;
|
|
#if LCD_DEPTH > 1
|
|
input_bmp.format = FORMAT_NATIVE;
|
|
#endif
|
|
input_bmp.data = (char*)buf;
|
|
scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
|
|
buf_size,
|
|
FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
|
|
&format_transposed);
|
|
if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Thread used for loading and preparing bitmaps in the background
|
|
*/
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
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_ = cache[ind_].next; \
|
|
} while (next_ != cache_used && (cond)); \
|
|
ind_; \
|
|
})
|
|
|
|
#define seek_left_while(start, cond) \
|
|
({ \
|
|
int ind_, next_ = (start); \
|
|
do { \
|
|
ind_ = next_; \
|
|
next_ = cache[ind_].prev; \
|
|
} while (ind_ != cache_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 = cache[i].prev;
|
|
int next = cache[i].next;
|
|
if (i == next)
|
|
{
|
|
*head = -1;
|
|
return -1;
|
|
}
|
|
else if (i == *head)
|
|
*head = next;
|
|
cache[next].prev = prev;
|
|
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 = cache[next].prev;
|
|
cache[next].prev = i;
|
|
cache[prev].next = i;
|
|
cache[i].next = next;
|
|
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;
|
|
cache[i].next = i;
|
|
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 = 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 (cache[i].hid != empty_slide_hid)
|
|
buflib_free(&buf_ctx, cache[i].hid);
|
|
cache[i].index = -1;
|
|
lla_pop_item(&cache_used, i);
|
|
lla_insert_tail(&cache_free, i);
|
|
if (cache_used == -1)
|
|
{
|
|
cache_right_index = -1;
|
|
cache_left_index = -1;
|
|
cache_center_index = -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 (cache_used == -1)
|
|
return false;
|
|
int i, l = cache_used, r = cache[cache_used].prev, prio_max;
|
|
int prio_l = cache[l].index < center_index ?
|
|
center_index - cache[l].index : 0;
|
|
int prio_r = cache[r].index > center_index ?
|
|
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 == cache_left_index)
|
|
cache_left_index = cache[i].next;
|
|
if (i == cache_right_index)
|
|
cache_right_index = cache[i].prev;
|
|
free_slide(i);
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Read the pfraw image given as filename and return the hid of the buffer
|
|
*/
|
|
int read_pfraw(char* filename, int prio)
|
|
{
|
|
struct pfraw_header bmph;
|
|
int fh = rb->open(filename, O_RDONLY);
|
|
if( fh < 0 ) {
|
|
cache_version = CACHE_UPDATE;
|
|
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;
|
|
while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
|
|
|
|
if (!hid) {
|
|
rb->close( fh );
|
|
return 0;
|
|
}
|
|
|
|
rb->yield(); /* allow audio to play when fast scrolling */
|
|
struct dim *bm = 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];
|
|
rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%x.pfraw",
|
|
mfnv(get_album_name(slide_index)));
|
|
|
|
int hid = read_pfraw(pfraw_file, prio);
|
|
if (!hid)
|
|
return false;
|
|
|
|
cache[cache_index].hid = hid;
|
|
|
|
if ( cache_index < SLIDE_CACHE_SIZE ) {
|
|
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 (cache_center_index != -1)
|
|
{
|
|
int next, prev;
|
|
if (cache[cache_center_index].index != center_index)
|
|
{
|
|
if (cache[cache_center_index].index < center_index)
|
|
{
|
|
cache_center_index = seek_right_while(cache_center_index,
|
|
cache[next_].index <= center_index);
|
|
prev = cache_center_index;
|
|
next = cache[cache_center_index].next;
|
|
}
|
|
else
|
|
{
|
|
cache_center_index = seek_left_while(cache_center_index,
|
|
cache[next_].index >= center_index);
|
|
next = cache_center_index;
|
|
prev = cache[cache_center_index].prev;
|
|
}
|
|
if (cache[cache_center_index].index != center_index)
|
|
{
|
|
if (cache_free == -1)
|
|
free_slide_prio(0);
|
|
i = lla_pop_head(&cache_free);
|
|
if (!load_and_prepare_surface(center_index, i, 0))
|
|
goto fail_and_refree;
|
|
if (cache[next].index == -1)
|
|
{
|
|
if (cache[prev].index == -1)
|
|
goto insert_first_slide;
|
|
else
|
|
next = cache[prev].next;
|
|
}
|
|
lla_insert(i, next);
|
|
if (cache[i].index < cache[cache_used].index)
|
|
cache_used = i;
|
|
cache_center_index = i;
|
|
cache_left_index = i;
|
|
cache_right_index = i;
|
|
return true;
|
|
}
|
|
}
|
|
if (cache[cache_left_index].index >
|
|
cache[cache_center_index].index)
|
|
cache_left_index = cache_center_index;
|
|
if (cache[cache_right_index].index <
|
|
cache[cache_center_index].index)
|
|
cache_right_index = cache_center_index;
|
|
cache_left_index = seek_left_while(cache_left_index,
|
|
cache[ind_].index - 1 == cache[next_].index);
|
|
cache_right_index = seek_right_while(cache_right_index,
|
|
cache[ind_].index - 1 == cache[next_].index);
|
|
int prio_l = cache[cache_center_index].index -
|
|
cache[cache_left_index].index + 1;
|
|
int prio_r = cache[cache_right_index].index -
|
|
cache[cache_center_index].index + 1;
|
|
if ((prio_l < prio_r ||
|
|
cache[cache_right_index].index >= number_of_slides) &&
|
|
cache[cache_left_index].index > 0)
|
|
{
|
|
if (cache_free == -1 && !free_slide_prio(prio_l))
|
|
return false;
|
|
i = lla_pop_head(&cache_free);
|
|
if (load_and_prepare_surface(cache[cache_left_index].index
|
|
- 1, i, prio_l))
|
|
{
|
|
lla_insert_before(&cache_used, i, cache_left_index);
|
|
cache_left_index = i;
|
|
return true;
|
|
}
|
|
} else if(cache[cache_right_index].index < number_of_slides - 1)
|
|
{
|
|
if (cache_free == -1 && !free_slide_prio(prio_r))
|
|
return false;
|
|
i = lla_pop_head(&cache_free);
|
|
if (load_and_prepare_surface(cache[cache_right_index].index
|
|
+ 1, i, prio_r))
|
|
{
|
|
lla_insert_after(i, cache_right_index);
|
|
cache_right_index = i;
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
i = lla_pop_head(&cache_free);
|
|
if (load_and_prepare_surface(center_index, i, 0))
|
|
{
|
|
insert_first_slide:
|
|
cache[i].next = i;
|
|
cache[i].prev = i;
|
|
cache_center_index = i;
|
|
cache_left_index = i;
|
|
cache_right_index = i;
|
|
cache_used = i;
|
|
return true;
|
|
}
|
|
}
|
|
fail_and_refree:
|
|
if (i != -1)
|
|
{
|
|
lla_insert_tail(&cache_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 = 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 = cache_used ) != -1)
|
|
{
|
|
do {
|
|
if (cache[i].index == slide_index)
|
|
return get_slide(cache[i].hid);
|
|
i = cache[i].next;
|
|
} while (i != cache_used);
|
|
}
|
|
return get_slide(empty_slide_hid);
|
|
}
|
|
|
|
/**
|
|
adjust slides so that they are in "steady state" position
|
|
*/
|
|
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 < num_slides; i++) {
|
|
struct slide_data *si = &left_slides[i];
|
|
si->angle = itilt;
|
|
si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
|
|
si->cy = offsetY;
|
|
si->slide_index = center_index - 1 - i;
|
|
si->distance = 0;
|
|
}
|
|
|
|
for (i = 0; i < num_slides; i++) {
|
|
struct slide_data *si = &right_slides[i];
|
|
si->angle = -itilt;
|
|
si->cx = offsetX + 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
|
|
*/
|
|
void recalc_offsets(void)
|
|
{
|
|
PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
|
|
PFreal zo;
|
|
PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
|
|
PFREAL_ONE) * 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 / 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 (ushort)
|
|
to an uint, multiply and compress the result back to a ushort.
|
|
*/
|
|
#if (LCD_PIXELFORMAT == RGB565SWAPPED)
|
|
static inline unsigned fade_color(pix_t c, unsigned a)
|
|
{
|
|
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
|
|
static inline unsigned fade_color(pix_t c, unsigned a)
|
|
{
|
|
unsigned int result;
|
|
a = (a + 2) & 0x1fc;
|
|
result = ((c & 0xf81f) * a) & 0xf81f00;
|
|
result |= ((c & 0x7e0) * a) & 0x7e000;
|
|
result >>= 8;
|
|
return result;
|
|
}
|
|
#else
|
|
static inline unsigned fade_color(pix_t c, unsigned a)
|
|
{
|
|
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.
|
|
*/
|
|
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 / 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 the 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
|
|
*/
|
|
void start_animation(void)
|
|
{
|
|
step = (target < center_slide.slide_index) ? -1 : 1;
|
|
pf_state = pf_scrolling;
|
|
}
|
|
|
|
/**
|
|
Go to the previous slide
|
|
*/
|
|
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
|
|
*/
|
|
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.
|
|
*/
|
|
void render_all_slides(void)
|
|
{
|
|
mylcd_set_background(G_BRIGHT(0));
|
|
/* TODO: Optimizes this by e.g. invalidating rects */
|
|
mylcd_clear_display();
|
|
|
|
int nleft = num_slides;
|
|
int nright = 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 && 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.
|
|
*/
|
|
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 < num_slides; i++)
|
|
left_slides[i].slide_index = center_index - 1 - i;
|
|
for (i = 0; i < 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 < num_slides; i++) {
|
|
struct slide_data *si = &left_slides[i];
|
|
si->angle = itilt;
|
|
si->cx =
|
|
-(offsetX + slide_spacing * i * PFREAL_ONE + step
|
|
* slide_spacing * ftick);
|
|
si->cy = offsetY;
|
|
}
|
|
|
|
for (i = 0; i < num_slides; i++) {
|
|
struct slide_data *si = &right_slides[i];
|
|
si->angle = -itilt;
|
|
si->cx =
|
|
offsetX + slide_spacing * i * PFREAL_ONE - step
|
|
* 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
|
|
*/
|
|
void cleanup(void)
|
|
{
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(false);
|
|
#endif
|
|
end_pf_thread();
|
|
/* Turn on backlight timeout (revert to settings) */
|
|
backlight_use_settings();
|
|
|
|
#ifdef USEGSLIB
|
|
grey_release();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
Shows the settings menu
|
|
*/
|
|
int settings_menu(void)
|
|
{
|
|
int selection = 0;
|
|
bool old_val;
|
|
|
|
MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
|
|
"Spacing", "Centre margin", "Number of slides", "Zoom",
|
|
"Show album title", "Resize Covers", "Rebuild cache",
|
|
"WPS Integration", "Backlight");
|
|
|
|
static const struct opt_items album_name_options[] = {
|
|
{ "Hide album title", -1 },
|
|
{ "Show at the bottom", -1 },
|
|
{ "Show at the top", -1 }
|
|
};
|
|
static const struct opt_items wps_options[] = {
|
|
{ "Off", -1 },
|
|
{ "Direct", -1 },
|
|
{ "Via Track list", -1 }
|
|
};
|
|
static const struct opt_items backlight_options[] = {
|
|
{ "Always On", -1 },
|
|
{ "Normal", -1 },
|
|
};
|
|
|
|
do {
|
|
selection=rb->do_menu(&settings_menu,&selection, NULL, false);
|
|
switch(selection) {
|
|
case 0:
|
|
rb->set_bool("Show FPS", &show_fps);
|
|
reset_track_list();
|
|
break;
|
|
|
|
case 1:
|
|
rb->set_int("Spacing between slides", "", 1,
|
|
&slide_spacing,
|
|
NULL, 1, 0, 100, NULL );
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
|
|
case 2:
|
|
rb->set_int("Centre margin", "", 1,
|
|
¢er_margin,
|
|
NULL, 1, 0, 80, NULL );
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
|
|
case 3:
|
|
rb->set_int("Number of slides", "", 1, &num_slides,
|
|
NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
|
|
case 4:
|
|
rb->set_int("Zoom", "", 1, &zoom,
|
|
NULL, 1, 10, 300, NULL );
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
case 5:
|
|
rb->set_option("Show album title", &show_album_name,
|
|
INT, album_name_options, 3, NULL);
|
|
reset_track_list();
|
|
recalc_offsets();
|
|
reset_slides();
|
|
break;
|
|
case 6:
|
|
old_val = resize;
|
|
rb->set_bool("Resize Covers", &resize);
|
|
if (old_val == resize) /* changed? */
|
|
break;
|
|
/* fallthrough if changed, since cache needs to be rebuilt */
|
|
case 7:
|
|
cache_version = CACHE_REBUILD;
|
|
rb->remove(EMPTY_SLIDE);
|
|
configfile_save(CONFIG_FILE, config,
|
|
CONFIG_NUM_ITEMS, CONFIG_VERSION);
|
|
rb->splash(HZ, "Cache will be rebuilt on next restart");
|
|
break;
|
|
case 8:
|
|
rb->set_option("WPS Integration", &auto_wps, INT, wps_options, 3, NULL);
|
|
break;
|
|
case 9:
|
|
rb->set_option("Backlight", &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,
|
|
};
|
|
|
|
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,
|
|
"Go to WPS",
|
|
#if PF_PLAYBACK_CAPABLE
|
|
"Clear playlist", "Playback Control",
|
|
#endif
|
|
"Settings", "Return", "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, "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
|
|
*/
|
|
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
|
|
*/
|
|
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;
|
|
selected_track_pulse = (selected_track_pulse+1) % 10;
|
|
int c2 = selected_track_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 (show_album_name)
|
|
{
|
|
case ALBUM_NAME_HIDE:
|
|
track_list_y = (show_fps ? char_height : 0);
|
|
track_list_h = LCD_HEIGHT - track_list_y;
|
|
break;
|
|
case ALBUM_NAME_BOTTOM:
|
|
track_list_y = (show_fps ? char_height : 0);
|
|
track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
|
|
break;
|
|
case ALBUM_NAME_TOP:
|
|
default:
|
|
track_list_y = char_height * 2;
|
|
track_list_h = LCD_HEIGHT - track_list_y -
|
|
(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);
|
|
track_list_visible_entries = fmin( track_list_h/char_height , track_count );
|
|
start_index_track_list = 0;
|
|
selected_track = 0;
|
|
last_selected_track = -1;
|
|
|
|
/* let the tracklist start more centered
|
|
* if the screen isn't filled with tracks */
|
|
total_height = track_count*char_height;
|
|
if (total_height < track_list_h)
|
|
{
|
|
track_list_y += (track_list_h - total_height) / 2;
|
|
track_list_h = total_height;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Display the list of tracks
|
|
*/
|
|
void show_track_list(void)
|
|
{
|
|
mylcd_clear_display();
|
|
if ( center_slide.slide_index != track_index ) {
|
|
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 = track_list_y;
|
|
int track_i;
|
|
track_i = start_index_track_list;
|
|
for (;track_i < track_list_visible_entries+start_index_track_list;
|
|
track_i++)
|
|
{
|
|
char *trackname = get_track_name(track_i);
|
|
if ( track_i == selected_track ) {
|
|
if (selected_track != last_selected_track) {
|
|
set_scroll_line(trackname, PF_SCROLL_TRACK);
|
|
last_selected_track = selected_track;
|
|
}
|
|
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;
|
|
color = 250 - (abs(selected_track - track_i) * 200 / track_count);
|
|
}
|
|
mylcd_set_foreground(G_BRIGHT(color));
|
|
mylcd_putsxy(titletxt_x,titletxt_y,trackname);
|
|
titletxt_y += titletxt_h;
|
|
}
|
|
}
|
|
|
|
void select_next_track(void)
|
|
{
|
|
if ( selected_track < track_count - 1 ) {
|
|
selected_track++;
|
|
if (selected_track==(track_list_visible_entries+start_index_track_list))
|
|
start_index_track_list++;
|
|
}
|
|
}
|
|
|
|
void select_prev_track(void)
|
|
{
|
|
if (selected_track > 0 ) {
|
|
if (selected_track==start_index_track_list) start_index_track_list--;
|
|
selected_track--;
|
|
}
|
|
}
|
|
|
|
#if PF_PLAYBACK_CAPABLE
|
|
/*
|
|
* Puts the current tracklist into a newly created playlist and starts playling
|
|
*/
|
|
void start_playback(bool append)
|
|
{
|
|
static int old_playlist = -1, old_shuffle = 0;
|
|
int count = 0;
|
|
int position = selected_track;
|
|
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 < track_count);
|
|
rb->playlist_sync(NULL);
|
|
}
|
|
else
|
|
return;
|
|
|
|
if (rb->global_settings->playlist_shuffle)
|
|
position = rb->playlist_shuffle(*rb->current_tick, selected_track);
|
|
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);
|
|
old_playlist = center_slide.slide_index;
|
|
old_shuffle = shuffle;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
Draw the current album name
|
|
*/
|
|
void draw_album_text(void)
|
|
{
|
|
if (show_album_name == ALBUM_NAME_HIDE)
|
|
return;
|
|
|
|
int albumtxt_index;
|
|
int char_height;
|
|
int albumtxt_x, albumtxt_y;
|
|
|
|
char *albumtxt;
|
|
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(albumtxt_index);
|
|
|
|
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();
|
|
if (show_album_name == ALBUM_NAME_TOP)
|
|
albumtxt_y = char_height / 2;
|
|
else
|
|
albumtxt_y = LCD_HEIGHT - char_height - char_height/2;
|
|
|
|
albumtxt_x = get_scroll_line_offset(PF_SCROLL_ALBUM);
|
|
mylcd_putsxy(albumtxt_x, albumtxt_y, albumtxt);
|
|
}
|
|
|
|
/**
|
|
Display an error message and wait for input.
|
|
*/
|
|
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.
|
|
*/
|
|
int main(void)
|
|
{
|
|
int ret;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
|
|
if(auto_wps == 0)
|
|
draw_splashscreen();
|
|
if(backlight_mode == 0) {
|
|
/* Turn off backlight timeout */
|
|
backlight_ignore_timeout();
|
|
}
|
|
|
|
init_scroll_lines();
|
|
init_reflect_table();
|
|
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
ret = create_album_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;
|
|
}
|
|
|
|
ALIGN_BUFFER(buf, buf_size, 4);
|
|
number_of_slides = album_count;
|
|
if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
|
|
cache_version = CACHE_REBUILD;
|
|
configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
|
|
error_wait("Could not create album art cache");
|
|
return PLUGIN_ERROR;
|
|
}
|
|
|
|
if (!create_empty_slide(cache_version != CACHE_VERSION)) {
|
|
cache_version = CACHE_REBUILD;
|
|
configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
|
|
error_wait("Could not load the empty slide");
|
|
return PLUGIN_ERROR;
|
|
}
|
|
if (cache_version != CACHE_VERSION)
|
|
{
|
|
cache_version = CACHE_VERSION;
|
|
configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
|
|
}
|
|
|
|
buflib_init(&buf_ctx, (void *)buf, buf_size);
|
|
|
|
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++) {
|
|
cache[i].hid = 0;
|
|
cache[i].index = 0;
|
|
cache[i].next = i + 1;
|
|
cache[i].prev = i - 1;
|
|
}
|
|
cache[0].prev = i - 1;
|
|
cache[i - 1].next = 0;
|
|
cache_free = 0;
|
|
buffer = LCD_BUF;
|
|
|
|
pf_state = pf_idle;
|
|
|
|
track_index = -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();
|
|
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 (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 (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 )
|
|
{
|
|
buflib_buffer_in(&buf_ctx, borrowed);
|
|
borrowed = 0;
|
|
track_index = -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 ( 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, "Added to playlist");
|
|
}
|
|
else if( pf_state == pf_show_tracks ) {
|
|
rb->playlist_insert_track(NULL, get_track_filename(selected_track),
|
|
PLAYLIST_INSERT_LAST, false, true);
|
|
rb->playlist_sync(NULL);
|
|
rb->splash(HZ*2, "Added to playlist");
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
case PF_TRACKLIST:
|
|
if ( 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(auto_wps == 1) {
|
|
create_track_index(center_slide.slide_index);
|
|
reset_track_list();
|
|
start_playback(false);
|
|
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(auto_wps != 0) {
|
|
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)
|
|
{
|
|
int ret;
|
|
(void) parameter;
|
|
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
|
|
|
|
ret = main();
|
|
if ( ret == PLUGIN_OK || ret == PLUGIN_GOTO_WPS) {
|
|
if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
|
|
CONFIG_VERSION))
|
|
{
|
|
rb->splash(HZ, "Error writing config.");
|
|
ret = PLUGIN_ERROR;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|