Make pictureflow compile again.
[maemo-rb.git] / apps / plugins / pictureflow / pictureflow.c
blobee92ab8737b434cef6877223c74c47e99fda8c23
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Jonas Hurrelmann (j@outpo.st)
11 * Copyright (C) 2007 Nicolas Pennequin
12 * Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) (original Qt Version)
14 * Original code: http://code.google.com/p/pictureflow/
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
24 ****************************************************************************/
26 #include "plugin.h"
27 #include <albumart.h>
28 #include "lib/read_image.h"
29 #include "lib/pluginlib_actions.h"
30 #include "lib/pluginlib_exit.h"
31 #include "lib/helper.h"
32 #include "lib/configfile.h"
33 #include "lib/grey.h"
34 #include "lib/mylcd.h"
35 #include "lib/feature_wrappers.h"
39 /******************************* Globals ***********************************/
42 * Targets which use plugin_get_audio_buffer() can't have playback from
43 * within pictureflow itself, as the whole core audio buffer is occupied */
44 #define PF_PLAYBACK_CAPABLE (PLUGIN_BUFFER_SIZE > 0x10000)
46 #if PF_PLAYBACK_CAPABLE
47 #include "lib/playback_control.h"
48 #endif
50 #define PF_PREV ACTION_STD_PREV
51 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
52 #define PF_NEXT ACTION_STD_NEXT
53 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
54 #define PF_SELECT ACTION_STD_OK
55 #define PF_CONTEXT ACTION_STD_CONTEXT
56 #define PF_BACK ACTION_STD_CANCEL
57 #define PF_MENU ACTION_STD_MENU
58 #define PF_WPS ACTION_TREE_WPS
60 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
61 #define PF_TRACKLIST (LAST_ACTION_PLACEHOLDER + 2)
63 #if defined(HAVE_SCROLLWHEEL) || CONFIG_KEYPAD == IRIVER_H10_PAD || \
64 CONFIG_KEYPAD == SAMSUNG_YH_PAD
65 #define USE_CORE_PREVNEXT
66 #endif
68 #ifndef USE_CORE_PREVNEXT
69 /* scrollwheel targets use the wheel, just as they do in lists,
70 * so there's no need for a special context,
71 * others use left/right here too (as oppsed to up/down in lists) */
72 const struct button_mapping pf_context_album_scroll[] =
74 #ifdef HAVE_TOUCHSCREEN
75 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
76 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
77 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
78 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
79 #endif
80 #if (CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD)
81 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
82 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
83 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
84 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
85 #else
86 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
87 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
88 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
89 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
90 {ACTION_NONE, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT},
91 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
92 {ACTION_NONE, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_LEFT},
93 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_RIGHT},
94 #endif
95 #if CONFIG_KEYPAD == ONDIO_PAD
96 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
97 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
98 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
99 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
100 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
101 #endif
102 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_PLUGIN|1)
104 #endif /* !USE_CORE_PREVNEXT */
106 const struct button_mapping pf_context_buttons[] =
108 #ifdef HAVE_TOUCHSCREEN
109 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
110 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
111 #endif
112 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
113 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
114 #elif CONFIG_KEYPAD == SANSA_C100_PAD
115 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
116 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
117 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
118 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
119 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
120 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
121 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
122 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
123 {PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE},
124 {PF_TRACKLIST, BUTTON_RIGHT, BUTTON_NONE},
125 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
127 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
128 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWON_D2_PAD
129 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
130 #if CONFIG_KEYPAD == COWON_D2_PAD
131 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
132 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
133 #endif
134 #elif CONFIG_KEYPAD == SANSA_E200_PAD
135 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
136 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
137 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
138 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
139 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
140 || (CONFIG_KEYPAD == IPOD_4G_PAD)
141 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
142 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
143 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
144 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
145 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
146 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
147 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
148 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
149 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
150 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
151 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
152 {PF_QUIT, BUTTON_REC, BUTTON_NONE},
153 #endif
154 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
155 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
156 #else
157 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
158 #endif
160 const struct button_mapping *pf_contexts[] =
162 #ifndef USE_CORE_PREVNEXT
163 pf_context_album_scroll,
164 #endif
165 pf_context_buttons
168 #if LCD_DEPTH < 8
169 #if LCD_DEPTH > 1
170 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
171 #else /* LCD_DEPTH <= 1 */
172 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
173 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
174 #define PICTUREFLOW_DRMODE DRMODE_SOLID
175 #else
176 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
177 #endif
178 #endif /* LCD_DEPTH <= 1 */
179 #define USEGSLIB
180 GREY_INFO_STRUCT
181 #define LCD_BUF _grey_info.buffer
182 #define G_PIX(r,g,b) \
183 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
184 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
185 #define G_BRIGHT(y) (y)
186 #define BUFFER_WIDTH _grey_info.width
187 #define BUFFER_HEIGHT _grey_info.height
188 typedef unsigned char pix_t;
189 #else /* LCD_DEPTH >= 8 */
190 #define LCD_BUF rb->lcd_framebuffer
191 #define G_PIX LCD_RGBPACK
192 #define N_PIX LCD_RGBPACK
193 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
194 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
195 #define BUFFER_WIDTH LCD_WIDTH
196 #define BUFFER_HEIGHT LCD_HEIGHT
197 typedef fb_data pix_t;
198 #endif /* LCD_DEPTH >= 8 */
200 /* for fixed-point arithmetic, we need minimum 32-bit long
201 long long (64-bit) might be useful for multiplication and division */
202 #define PFreal long
203 #define PFREAL_SHIFT 10
204 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
205 #define PFREAL_ONE (1 << PFREAL_SHIFT)
206 #define PFREAL_HALF (PFREAL_ONE >> 1)
209 #define IANGLE_MAX 1024
210 #define IANGLE_MASK 1023
212 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
213 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
214 #define DISPLAY_HEIGHT REFLECT_TOP
215 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
216 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
217 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
218 (REFLECT_HEIGHT * 5))
219 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
220 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
221 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
222 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
223 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
225 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
227 #define MAX_SLIDES_COUNT 10
229 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
230 #define CACHE_PREFIX PLUGIN_DEMOS_DATA_DIR "/pictureflow"
232 #define EV_EXIT 9999
233 #define EV_WAKEUP 1337
235 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
236 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
237 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
239 /* some magic numbers for cache_version. */
240 #define CACHE_REBUILD 0
241 #define CACHE_UPDATE 1
243 /* Error return values */
244 #define ERROR_NO_ALBUMS -1
245 #define ERROR_BUFFER_FULL -2
247 /* current version for cover cache */
248 #define CACHE_VERSION 3
249 #define CONFIG_VERSION 1
250 #define CONFIG_FILE "pictureflow.cfg"
252 /** structs we use */
254 struct slide_data {
255 int slide_index;
256 int angle;
257 PFreal cx;
258 PFreal cy;
259 PFreal distance;
262 struct slide_cache {
263 int index; /* index of the cached slide */
264 int hid; /* handle ID of the cached slide */
265 short next; /* "next" slide, with LRU last */
266 short prev; /* "previous" slide */
269 struct album_data {
270 int name_idx;
271 long seek;
274 struct track_data {
275 uint32_t sort;
276 int name_idx; /* offset to the track name */
277 long seek;
278 #if PF_PLAYBACK_CAPABLE
279 /* offset to the filename in the string, needed for playlist generation */
280 int filename_idx;
281 #endif
284 struct rect {
285 int left;
286 int right;
287 int top;
288 int bottom;
291 struct load_slide_event_data {
292 int slide_index;
293 int cache_index;
296 enum pf_scroll_line_type {
297 PF_SCROLL_TRACK = 0,
298 PF_SCROLL_ALBUM,
299 PF_MAX_SCROLL_LINES
302 struct pf_scroll_line_info {
303 long ticks; /* number of ticks between each move */
304 long delay; /* number of ticks to delay starting scrolling */
305 int step; /* pixels to move */
306 long next_scroll; /* tick of the next move */
309 struct pf_scroll_line {
310 int width; /* width of the string */
311 int offset; /* x coordinate of the string */
312 int step; /* 0 if scroll is disabled. otherwise, pixels to move */
313 long start_tick; /* tick when to start scrolling */
316 struct pfraw_header {
317 int32_t width; /* bmap width in pixels */
318 int32_t height; /* bmap height in pixels */
321 enum show_album_name_values {
322 ALBUM_NAME_HIDE = 0,
323 ALBUM_NAME_BOTTOM,
324 ALBUM_NAME_TOP
326 static char* show_album_name_conf[] =
328 "hide",
329 "bottom",
330 "top"
333 #define MAX_SPACING 40
334 #define MAX_MARGIN 80
336 /* config values and their defaults */
337 static int slide_spacing = DISPLAY_WIDTH / 4;
338 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
339 static int num_slides = 4;
340 static int zoom = 100;
341 static bool show_fps = false;
342 static int auto_wps = 0;
343 static int last_album = 0;
344 static int backlight_mode = 0;
345 static bool resize = true;
346 static int cache_version = 0;
347 static int show_album_name = (LCD_HEIGHT > 100)
348 ? ALBUM_NAME_TOP : ALBUM_NAME_BOTTOM;
350 static struct configdata config[] =
352 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
353 NULL },
354 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
355 NULL },
356 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
357 NULL },
358 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
359 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
360 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
361 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
362 { TYPE_ENUM, 0, 3, { .int_p = &show_album_name }, "show album name",
363 show_album_name_conf },
364 { TYPE_INT, 0, 2, { .int_p = &auto_wps }, "auto wps", NULL },
365 { TYPE_INT, 0, 999999, { .int_p = &last_album }, "last album", NULL },
366 { TYPE_INT, 0, 1, { .int_p = &backlight_mode }, "backlight", NULL }
369 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
371 /** below we allocate the memory we want to use **/
373 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
374 static uint8_t reflect_table[REFLECT_HEIGHT];
375 static struct slide_data center_slide;
376 static struct slide_data left_slides[MAX_SLIDES_COUNT];
377 static struct slide_data right_slides[MAX_SLIDES_COUNT];
378 static int slide_frame;
379 static int step;
380 static int target;
381 static int fade;
382 static int center_index = 0; /* index of the slide that is in the center */
383 static int itilt;
384 static PFreal offsetX;
385 static PFreal offsetY;
386 static int number_of_slides;
388 static struct slide_cache cache[SLIDE_CACHE_SIZE];
389 static int cache_free;
390 static int cache_used = -1;
391 static int cache_left_index = -1;
392 static int cache_right_index = -1;
393 static int cache_center_index = -1;
395 /* use long for aligning */
396 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
397 /* queue (as array) for scheduling load_surface */
399 static int empty_slide_hid;
401 unsigned int thread_id;
402 struct event_queue thread_q;
404 static struct tagcache_search tcs;
406 static struct buflib_context buf_ctx;
408 static struct album_data *album;
409 static char *album_names;
410 static int album_count;
412 static struct track_data *tracks;
413 static char *track_names;
414 static size_t borrowed = 0;
415 static int track_count;
416 static int track_index;
417 static int selected_track;
418 static int selected_track_pulse;
419 void reset_track_list(void);
421 void * buf;
422 size_t buf_size;
424 static bool thread_is_running;
426 static int cover_animation_keyframe;
427 static int extra_fade;
429 static struct pf_scroll_line_info scroll_line_info;
430 static struct pf_scroll_line scroll_lines[PF_MAX_SCROLL_LINES];
431 static int prev_albumtxt_index = -1;
432 static int last_selected_track = -1;
434 static int start_index_track_list = 0;
435 static int track_list_visible_entries = 0;
436 static int track_list_y;
437 static int track_list_h;
440 Proposals for transitions:
442 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
443 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
445 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
447 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
449 TODO:
450 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
451 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
453 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
455 enum pf_states {
456 pf_idle = 0,
457 pf_scrolling,
458 pf_cover_in,
459 pf_show_tracks,
460 pf_cover_out
463 static int pf_state;
465 /** code */
466 static bool free_slide_prio(int prio);
467 static inline unsigned fade_color(pix_t c, unsigned a);
468 bool load_new_slide(void);
469 int load_surface(int);
471 static inline PFreal fmul(PFreal a, PFreal b)
473 return (a*b) >> PFREAL_SHIFT;
477 * This version preshifts each operand, which is useful when we know how many
478 * of the least significant bits will be empty, or are worried about overflow
479 * in a particular calculation
481 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
483 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
486 /* ARMv5+ has a clz instruction equivalent to our function.
488 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
489 static inline int clz(uint32_t v)
491 return __builtin_clz(v);
494 /* Otherwise, use our clz, which can be inlined */
495 #elif defined(CPU_COLDFIRE)
496 /* This clz is based on the log2(n) implementation at
497 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
498 * A clz benchmark plugin showed this to be about 14% faster on coldfire
499 * than the LUT-based version.
501 static inline int clz(uint32_t v)
503 int r = 32;
504 if (v >= 0x10000)
506 v >>= 16;
507 r -= 16;
509 if (v & 0xff00)
511 v >>= 8;
512 r -= 8;
514 if (v & 0xf0)
516 v >>= 4;
517 r -= 4;
519 if (v & 0xc)
521 v >>= 2;
522 r -= 2;
524 if (v & 2)
526 v >>= 1;
527 r -= 1;
529 r -= v;
530 return r;
532 #else
533 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
534 0, 0, 0, 0, 0, 0, 0, 0 };
535 /* This clz is based on the log2(n) implementation at
536 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
537 * It is not any faster than the one above, but trades 16B in the lookup table
538 * for a savings of 12B per each inlined call.
540 static inline int clz(uint32_t v)
542 int r = 28;
543 if (v >= 0x10000)
545 v >>= 16;
546 r -= 16;
548 if (v & 0xff00)
550 v >>= 8;
551 r -= 8;
553 if (v & 0xf0)
555 v >>= 4;
556 r -= 4;
558 return r + clz_lut[v];
560 #endif
562 /* Return the maximum possible left shift for a signed int32, without
563 * overflow
565 static inline int allowed_shift(int32_t val)
567 uint32_t uval = val ^ (val >> 31);
568 return clz(uval) - 1;
571 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
572 * num and den before dividing.
574 static inline PFreal fdiv(PFreal num, PFreal den)
576 int shift = allowed_shift(num);
577 shift = MIN(PFREAL_SHIFT, shift);
578 num <<= shift;
579 den >>= PFREAL_SHIFT - shift;
580 return num / den;
583 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
584 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
585 #define fabs(a) (a < 0 ? -a : a)
586 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
588 #if CONFIG_CPU == SH7034
589 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
590 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
591 #else
592 #define MULUQ(a, b) ((a) * (b))
593 #endif
596 #if 0
597 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
598 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
600 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
601 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
603 static inline PFreal fmul(PFreal a, PFreal b)
605 return (a*b) >> PFREAL_SHIFT;
608 static inline PFreal fdiv(PFreal n, PFreal m)
610 return (n<<(PFREAL_SHIFT))/m;
612 #endif
614 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
615 static const short sin_tab[] = {
616 0, 100, 200, 297, 392, 483, 569, 650,
617 724, 792, 851, 903, 946, 980, 1004, 1019,
618 1024, 1019, 1004, 980, 946, 903, 851, 792,
619 724, 650, 569, 483, 392, 297, 200, 100,
620 0, -100, -200, -297, -392, -483, -569, -650,
621 -724, -792, -851, -903, -946, -980, -1004, -1019,
622 -1024, -1019, -1004, -980, -946, -903, -851, -792,
623 -724, -650, -569, -483, -392, -297, -200, -100,
627 static inline PFreal fsin(int iangle)
629 iangle &= IANGLE_MASK;
631 int i = (iangle >> 4);
632 PFreal p = sin_tab[i];
633 PFreal q = sin_tab[(i+1)];
634 PFreal g = (q - p);
635 return p + g * (iangle-i*16)/16;
638 static inline PFreal fcos(int iangle)
640 return fsin(iangle + (IANGLE_MAX >> 2));
643 static inline unsigned scale_val(unsigned val, unsigned bits)
645 val = val * ((1 << bits) - 1);
646 return ((val >> 8) + val + 128) >> 8;
649 static void output_row_8_transposed(uint32_t row, void * row_in,
650 struct scaler_context *ctx)
652 pix_t *dest = (pix_t*)ctx->bm->data + row;
653 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
654 #ifdef USEGSLIB
655 uint8_t *qp = (uint8_t*)row_in;
656 for (; dest < end; dest += ctx->bm->height)
657 *dest = *qp++;
658 #else
659 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
660 unsigned r, g, b;
661 for (; dest < end; dest += ctx->bm->height)
663 r = scale_val(qp->red, 5);
664 g = scale_val(qp->green, 6);
665 b = scale_val((qp++)->blue, 5);
666 *dest = LCD_RGBPACK_LCD(r,g,b);
668 #endif
671 /* read_image_file() is called without FORMAT_TRANSPARENT so
672 * it's safe to ignore alpha channel in the next two functions */
673 static void output_row_32_transposed(uint32_t row, void * row_in,
674 struct scaler_context *ctx)
676 pix_t *dest = (pix_t*)ctx->bm->data + row;
677 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
678 #ifdef USEGSLIB
679 uint32_t *qp = (uint32_t*)row_in;
680 for (; dest < end; dest += ctx->bm->height)
681 *dest = SC_OUT(*qp++, ctx);
682 #else
683 struct uint32_argb *qp = (struct uint32_argb*)row_in;
684 int r, g, b;
685 for (; dest < end; dest += ctx->bm->height)
687 r = scale_val(SC_OUT(qp->r, ctx), 5);
688 g = scale_val(SC_OUT(qp->g, ctx), 6);
689 b = scale_val(SC_OUT(qp->b, ctx), 5);
690 qp++;
691 *dest = LCD_RGBPACK_LCD(r,g,b);
693 #endif
696 #ifdef HAVE_LCD_COLOR
697 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
698 struct scaler_context *ctx)
700 pix_t *dest = (pix_t*)ctx->bm->data + row;
701 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
702 struct uint32_argb *qp = (struct uint32_argb*)row_in;
703 for (; dest < end; dest += ctx->bm->height)
705 unsigned r, g, b, y, u, v;
706 y = SC_OUT(qp->b, ctx);
707 u = SC_OUT(qp->g, ctx);
708 v = SC_OUT(qp->r, ctx);
709 qp++;
710 yuv_to_rgb(y, u, v, &r, &g, &b);
711 r = scale_val(r, 5);
712 g = scale_val(g, 6);
713 b = scale_val(b, 5);
714 *dest = LCD_RGBPACK_LCD(r, g, b);
717 #endif
719 static unsigned int get_size(struct bitmap *bm)
721 return bm->width * bm->height * sizeof(pix_t);
724 const struct custom_format format_transposed = {
725 .output_row_8 = output_row_8_transposed,
726 #ifdef HAVE_LCD_COLOR
727 .output_row_32 = {
728 output_row_32_transposed,
729 output_row_32_transposed_fromyuv
731 #else
732 .output_row_32 = output_row_32_transposed,
733 #endif
734 .get_size = get_size
737 static const struct button_mapping* get_context_map(int context)
739 return pf_contexts[context & ~CONTEXT_PLUGIN];
742 /* scrolling */
743 static void init_scroll_lines(void)
745 int i;
746 static const char scroll_tick_table[16] = {
747 /* Hz values:
748 1, 1.25, 1.55, 2, 2.5, 3.12, 4, 5, 6.25, 8.33, 10, 12.5, 16.7, 20, 25, 33 */
749 100, 80, 64, 50, 40, 32, 25, 20, 16, 12, 10, 8, 6, 5, 4, 3
752 scroll_line_info.ticks = scroll_tick_table[rb->global_settings->scroll_speed];
753 scroll_line_info.step = rb->global_settings->scroll_step;
754 scroll_line_info.delay = rb->global_settings->scroll_delay / (HZ / 10);
755 scroll_line_info.next_scroll = *rb->current_tick;
756 for (i = 0; i < PF_MAX_SCROLL_LINES; i++)
757 scroll_lines[i].step = 0;
760 static void set_scroll_line(const char *str, enum pf_scroll_line_type type)
762 struct pf_scroll_line *s = &scroll_lines[type];
763 s->width = mylcd_getstringsize(str, NULL, NULL);
764 s->step = 0;
765 s->offset = 0;
766 s->start_tick = *rb->current_tick + scroll_line_info.delay;
767 if (LCD_WIDTH - s->width < 0)
768 s->step = scroll_line_info.step;
769 else
770 s->offset = (LCD_WIDTH - s->width) / 2;
773 static int get_scroll_line_offset(enum pf_scroll_line_type type)
775 return scroll_lines[type].offset;
778 static void update_scroll_lines(void)
780 int i;
782 if (TIME_BEFORE(*rb->current_tick, scroll_line_info.next_scroll))
783 return;
785 scroll_line_info.next_scroll = *rb->current_tick + scroll_line_info.ticks;
787 for (i = 0; i < PF_MAX_SCROLL_LINES; i++)
789 struct pf_scroll_line *s = &scroll_lines[i];
790 if (s->step && TIME_BEFORE(s->start_tick, *rb->current_tick))
792 s->offset -= s->step;
794 if (s->offset >= 0) {
795 /* at beginning of line */
796 s->offset = 0;
797 s->step = scroll_line_info.step;
798 s->start_tick = *rb->current_tick + scroll_line_info.delay * 2;
800 if (s->offset <= LCD_WIDTH - s->width) {
801 /* at end of line */
802 s->offset = LCD_WIDTH - s->width;
803 s->step = -scroll_line_info.step;
804 s->start_tick = *rb->current_tick + scroll_line_info.delay * 2;
810 /* Create the lookup table with the scaling values for the reflections */
811 void init_reflect_table(void)
813 int i;
814 for (i = 0; i < REFLECT_HEIGHT; i++)
815 reflect_table[i] =
816 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
817 (5 * REFLECT_HEIGHT);
821 Create an index of all albums from the database.
822 Also store the album names so we can access them later.
824 int create_album_index(void)
826 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
827 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
828 album_count = 0;
829 rb->tagcache_search(&tcs, tag_album);
830 unsigned int l, name_idx = 0;
831 album_names = buf;
832 while (rb->tagcache_get_next(&tcs))
834 buf_size -= sizeof(struct album_data);
835 l = tcs.result_len;
836 album[-album_count].name_idx = name_idx;
838 if ( l > buf_size )
839 /* not enough memory */
840 return ERROR_BUFFER_FULL;
842 rb->strcpy(buf, tcs.result);
843 buf_size -= l;
844 buf = l + (char *)buf;
845 album[-album_count].seek = tcs.result_seek;
846 name_idx += l;
847 album_count++;
849 rb->tagcache_search_finish(&tcs);
850 ALIGN_BUFFER(buf, buf_size, 4);
851 int i;
852 struct album_data* tmp_album = (struct album_data*)buf;
853 for (i = album_count - 1; i >= 0; i--)
854 tmp_album[i] = album[-i];
855 album = tmp_album;
856 buf = album + album_count;
857 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
861 Return a pointer to the album name of the given slide_index
863 char* get_album_name(const int slide_index)
865 return album_names + album[slide_index].name_idx;
869 Return a pointer to the track name of the active album
870 create_track_index has to be called first.
872 char* get_track_name(const int track_index)
874 if ( track_index < track_count )
875 return track_names + tracks[track_index].name_idx;
876 return 0;
878 #if PF_PLAYBACK_CAPABLE
879 char* get_track_filename(const int track_index)
881 if ( track_index < track_count )
882 return track_names + tracks[track_index].filename_idx;
883 return 0;
885 #endif
887 int get_wps_current_index(void)
889 struct mp3entry *id3 = rb->audio_current_track();
890 if(id3 && id3->album) {
891 int i;
892 for( i=0; i < album_count; i++ )
894 if(!rb->strcmp(album_names + album[i].name_idx, id3->album))
895 return i;
898 return last_album;
902 Compare two unsigned ints passed via pointers.
904 int compare_tracks (const void *a_v, const void *b_v)
906 uint32_t a = ((struct track_data *)a_v)->sort;
907 uint32_t b = ((struct track_data *)b_v)->sort;
908 return (int)(a - b);
912 Create the track index of the given slide_index.
914 void create_track_index(const int slide_index)
916 if ( slide_index == track_index )
917 return;
918 track_index = slide_index;
920 if (!rb->tagcache_search(&tcs, tag_title))
921 goto fail;
923 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
924 track_count=0;
925 int string_index = 0, track_num;
926 int disc_num;
927 size_t out = 0;
928 track_names = rb->buflib_buffer_out(&buf_ctx, &out);
929 borrowed += out;
930 int avail = borrowed;
931 tracks = (struct track_data*)(track_names + borrowed);
932 while (rb->tagcache_get_next(&tcs))
934 int len = 0, fn_idx = 0;
936 avail -= sizeof(struct track_data);
937 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber);
938 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
940 if (disc_num < 0)
941 disc_num = 0;
942 retry:
943 if (track_num > 0)
945 if (disc_num)
946 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
947 "%d.%02d: %s", disc_num, track_num, tcs.result);
948 else
949 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
950 "%d: %s", track_num, tcs.result);
952 else
954 track_num = 0;
955 fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
956 "%s", tcs.result);
958 if (fn_idx <= 0)
959 goto fail;
960 #if PF_PLAYBACK_CAPABLE
961 int remain = avail - fn_idx;
962 if (remain >= MAX_PATH)
963 { /* retrieve filename for building the playlist */
964 rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
965 track_names + string_index + fn_idx, remain);
966 len = fn_idx + rb->strlen(track_names + string_index + fn_idx) + 1;
967 /* make sure track name and file name are really split by a \0, else
968 * get_track_name might fail */
969 *(track_names + string_index + fn_idx -1) = '\0';
972 else /* request more buffer so that track and filename fit */
973 len = (avail - remain) + MAX_PATH;
974 #else
975 len = fn_idx;
976 #endif
977 if (len > avail)
979 while (len > avail)
981 if (!free_slide_prio(0))
982 goto fail;
983 out = 0;
984 rb->buflib_buffer_out(&buf_ctx, &out);
985 avail += out;
986 borrowed += out;
988 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
989 unsigned int bytes = track_count * sizeof(struct track_data);
990 if (track_count)
991 rb->memmove(new_tracks, tracks, bytes);
992 tracks = new_tracks;
994 goto retry;
997 avail -= len;
998 tracks--;
999 tracks->sort = (disc_num << 24) + (track_num << 14) + track_count;
1000 tracks->name_idx = string_index;
1001 tracks->seek = tcs.result_seek;
1002 #if PF_PLAYBACK_CAPABLE
1003 tracks->filename_idx = fn_idx + string_index;
1004 #endif
1005 track_count++;
1006 string_index += len;
1009 rb->tagcache_search_finish(&tcs);
1011 /* now fix the track list order */
1012 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
1013 return;
1014 fail:
1015 track_count = 0;
1016 return;
1020 Determine filename of the album art for the given slide_index and
1021 store the result in buf.
1022 The algorithm looks for the first track of the given album uses
1023 find_albumart to find the filename.
1025 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
1026 int buflen)
1028 if ( slide_index == -1 )
1030 rb->strlcpy( buf, EMPTY_SLIDE, buflen );
1033 if (!rb->tagcache_search(&tcs, tag_filename))
1034 return false;
1036 bool result;
1037 /* find the first track of the album */
1038 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
1040 if ( rb->tagcache_get_next(&tcs) ) {
1041 struct mp3entry id3;
1042 int fd;
1044 #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
1045 if (rb->tagcache_fill_tags(&id3, tcs.result))
1047 rb->strlcpy(id3.path, tcs.result, sizeof(id3.path));
1049 else
1050 #endif
1052 fd = rb->open(tcs.result, O_RDONLY);
1053 rb->get_metadata(&id3, fd, tcs.result);
1054 rb->close(fd);
1056 if ( search_albumart_files(&id3, ":", buf, buflen) )
1057 result = true;
1058 else
1059 result = false;
1061 else {
1062 /* did not find a matching track */
1063 result = false;
1065 rb->tagcache_search_finish(&tcs);
1066 return result;
1070 Draw the PictureFlow logo
1072 void draw_splashscreen(void)
1074 unsigned char * buf_tmp = buf;
1075 size_t buf_tmp_size = buf_size;
1076 struct screen* display = rb->screens[SCREEN_MAIN];
1077 #if FB_DATA_SZ > 1
1078 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
1079 #endif
1080 struct bitmap logo = {
1081 #if LCD_WIDTH < 200
1082 .width = 100,
1083 .height = 18,
1084 #else
1085 .width = 193,
1086 .height = 34,
1087 #endif
1088 .data = buf_tmp
1090 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size,
1091 FORMAT_NATIVE, NULL);
1092 #if LCD_DEPTH > 1
1093 rb->lcd_set_background(N_BRIGHT(0));
1094 rb->lcd_set_foreground(N_BRIGHT(255));
1095 #else
1096 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
1097 #endif
1098 rb->lcd_clear_display();
1100 if (ret > 0)
1102 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
1103 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
1104 #endif
1105 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
1106 logo.width, logo.height);
1107 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
1108 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
1109 #endif
1112 rb->lcd_update();
1117 Draw a simple progress bar
1119 void draw_progressbar(int step)
1121 int txt_w, txt_h;
1122 const int bar_height = 22;
1123 const int w = LCD_WIDTH - 20;
1124 const int x = 10;
1126 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
1128 int y = (LCD_HEIGHT - txt_h)/2;
1130 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
1131 y += (txt_h + 5);
1133 #if LCD_DEPTH > 1
1134 rb->lcd_set_foreground(N_BRIGHT(100));
1135 #endif
1136 rb->lcd_drawrect(x, y, w+2, bar_height);
1137 #if LCD_DEPTH > 1
1138 rb->lcd_set_foreground(N_PIX(165, 231, 82));
1139 #endif
1141 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
1142 #if LCD_DEPTH > 1
1143 rb->lcd_set_foreground(N_BRIGHT(255));
1144 #endif
1145 rb->lcd_update();
1146 rb->yield();
1149 /* Calculate modified FNV hash of string
1150 * has good avalanche behaviour and uniform distribution
1151 * see http://home.comcast.net/~bretm/hash/ */
1152 unsigned int mfnv(char *str)
1154 const unsigned int p = 16777619;
1155 unsigned int hash = 0x811C9DC5; // 2166136261;
1157 while(*str)
1158 hash = (hash ^ *str++) * p;
1159 hash += hash << 13;
1160 hash ^= hash >> 7;
1161 hash += hash << 3;
1162 hash ^= hash >> 17;
1163 hash += hash << 5;
1164 return hash;
1168 Save the given bitmap as filename in the pfraw format
1170 bool save_pfraw(char* filename, struct bitmap *bm)
1172 struct pfraw_header bmph;
1173 bmph.width = bm->width;
1174 bmph.height = bm->height;
1175 int fh = rb->creat( filename , 0666);
1176 if( fh < 0 ) return false;
1177 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1178 pix_t *data = (pix_t*)( bm->data );
1179 int y;
1180 for( y = 0; y < bm->height; y++ )
1182 rb->write( fh, data , sizeof( pix_t ) * bm->width );
1183 data += bm->width;
1185 rb->close( fh );
1186 return true;
1190 Precomupte the album art images and store them in CACHE_PREFIX.
1191 Use the "?" bitmap if image is not found.
1193 bool create_albumart_cache(void)
1195 int ret;
1197 int i, slides = 0;
1198 struct bitmap input_bmp;
1200 char pfraw_file[MAX_PATH];
1201 char albumart_file[MAX_PATH];
1202 unsigned int format = FORMAT_NATIVE;
1203 bool update = (cache_version == CACHE_UPDATE);
1204 if (resize)
1205 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1206 for (i=0; i < album_count; i++)
1208 draw_progressbar(i);
1210 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%x.pfraw",
1211 mfnv(get_album_name(i)));
1212 /* delete existing cache, so it's a true rebuild */
1213 if(rb->file_exists(pfraw_file)) {
1214 if(update) {
1215 slides++;
1216 continue;
1218 rb->remove(pfraw_file);
1220 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1221 rb->strcpy(albumart_file, EMPTY_SLIDE_BMP);
1223 input_bmp.data = buf;
1224 input_bmp.width = DISPLAY_WIDTH;
1225 input_bmp.height = DISPLAY_HEIGHT;
1226 ret = read_image_file(albumart_file, &input_bmp, buf_size,
1227 format, &format_transposed);
1228 if (ret <= 0) {
1229 rb->splashf(HZ, "Album art is bad: %s", get_album_name(i));
1230 rb->strcpy(albumart_file, EMPTY_SLIDE_BMP);
1231 ret = read_image_file(albumart_file, &input_bmp, buf_size,
1232 format, &format_transposed);
1233 if(ret <= 0)
1234 continue;
1236 if (!save_pfraw(pfraw_file, &input_bmp))
1238 rb->splash(HZ, "Could not write bmp");
1239 continue;
1241 slides++;
1242 if ( rb->button_get(false) == PF_MENU ) return false;
1244 draw_progressbar(i);
1245 if ( slides == 0 ) {
1246 /* Warn the user that we couldn't find any albumart */
1247 rb->splash(2*HZ, "No album art found");
1248 return false;
1250 return true;
1254 Create the "?" slide, that is shown while loading
1255 or when no cover was found.
1257 int create_empty_slide(bool force)
1259 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1260 struct bitmap input_bmp;
1261 input_bmp.width = DISPLAY_WIDTH;
1262 input_bmp.height = DISPLAY_HEIGHT;
1263 #if LCD_DEPTH > 1
1264 input_bmp.format = FORMAT_NATIVE;
1265 #endif
1266 input_bmp.data = (char*)buf;
1267 scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
1268 buf_size,
1269 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
1270 &format_transposed);
1271 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
1272 return false;
1275 return true;
1279 Thread used for loading and preparing bitmaps in the background
1281 void thread(void)
1283 long sleep_time = 5 * HZ;
1284 struct queue_event ev;
1285 while (1) {
1286 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1287 switch (ev.id) {
1288 case EV_EXIT:
1289 return;
1290 case EV_WAKEUP:
1291 /* we just woke up */
1292 break;
1294 if(ev.id != SYS_TIMEOUT)
1295 while ( load_new_slide() ) {
1296 rb->yield();
1297 switch (ev.id) {
1298 case EV_EXIT:
1299 return;
1307 End the thread by posting the EV_EXIT event
1309 void end_pf_thread(void)
1311 if ( thread_is_running ) {
1312 rb->queue_post(&thread_q, EV_EXIT, 0);
1313 rb->thread_wait(thread_id);
1314 /* remove the thread's queue from the broadcast list */
1315 rb->queue_delete(&thread_q);
1316 thread_is_running = false;
1322 Create the thread an setup the event queue
1324 bool create_pf_thread(void)
1326 /* put the thread's queue in the bcast list */
1327 rb->queue_init(&thread_q, true);
1328 if ((thread_id = rb->create_thread(
1329 thread,
1330 thread_stack,
1331 sizeof(thread_stack),
1333 "Picture load thread"
1334 IF_PRIO(, PRIORITY_BUFFERING)
1335 IF_COP(, CPU)
1337 ) == 0) {
1338 return false;
1340 thread_is_running = true;
1341 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1342 return true;
1347 * The following functions implement the linked-list-in-array used to manage
1348 * the LRU cache of slides, and the list of free cache slots.
1351 #define seek_right_while(start, cond) \
1352 ({ \
1353 int ind_, next_ = (start); \
1354 do { \
1355 ind_ = next_; \
1356 next_ = cache[ind_].next; \
1357 } while (next_ != cache_used && (cond)); \
1358 ind_; \
1361 #define seek_left_while(start, cond) \
1362 ({ \
1363 int ind_, next_ = (start); \
1364 do { \
1365 ind_ = next_; \
1366 next_ = cache[ind_].prev; \
1367 } while (ind_ != cache_used && (cond)); \
1368 ind_; \
1372 Pop the given item from the linked list starting at *head, returning the next
1373 item, or -1 if the list is now empty.
1375 static inline int lla_pop_item (int *head, int i)
1377 int prev = cache[i].prev;
1378 int next = cache[i].next;
1379 if (i == next)
1381 *head = -1;
1382 return -1;
1384 else if (i == *head)
1385 *head = next;
1386 cache[next].prev = prev;
1387 cache[prev].next = next;
1388 return next;
1393 Pop the head item from the list starting at *head, returning the index of the
1394 item, or -1 if the list is already empty.
1396 static inline int lla_pop_head (int *head)
1398 int i = *head;
1399 if (i != -1)
1400 lla_pop_item(head, i);
1401 return i;
1405 Insert the item at index i before the one at index p.
1407 static inline void lla_insert (int i, int p)
1409 int next = p;
1410 int prev = cache[next].prev;
1411 cache[next].prev = i;
1412 cache[prev].next = i;
1413 cache[i].next = next;
1414 cache[i].prev = prev;
1419 Insert the item at index i at the end of the list starting at *head.
1421 static inline void lla_insert_tail (int *head, int i)
1423 if (*head == -1)
1425 *head = i;
1426 cache[i].next = i;
1427 cache[i].prev = i;
1428 } else
1429 lla_insert(i, *head);
1433 Insert the item at index i before the one at index p.
1435 static inline void lla_insert_after(int i, int p)
1437 p = cache[p].next;
1438 lla_insert(i, p);
1443 Insert the item at index i before the one at index p in the list starting at
1444 *head
1446 static inline void lla_insert_before(int *head, int i, int p)
1448 lla_insert(i, p);
1449 if (*head == p)
1450 *head = i;
1455 Free the used slide at index i, and its buffer, and move it to the free
1456 slides list.
1458 static inline void free_slide(int i)
1460 if (cache[i].hid != empty_slide_hid)
1461 rb->buflib_free(&buf_ctx, cache[i].hid);
1462 cache[i].index = -1;
1463 lla_pop_item(&cache_used, i);
1464 lla_insert_tail(&cache_free, i);
1465 if (cache_used == -1)
1467 cache_right_index = -1;
1468 cache_left_index = -1;
1469 cache_center_index = -1;
1475 Free one slide ranked above the given priority. If no such slide can be found,
1476 return false.
1478 static bool free_slide_prio(int prio)
1480 if (cache_used == -1)
1481 return false;
1482 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1483 int prio_l = cache[l].index < center_index ?
1484 center_index - cache[l].index : 0;
1485 int prio_r = cache[r].index > center_index ?
1486 cache[r].index - center_index : 0;
1487 if (prio_l > prio_r)
1489 i = l;
1490 prio_max = prio_l;
1491 } else {
1492 i = r;
1493 prio_max = prio_r;
1495 if (prio_max > prio)
1497 if (i == cache_left_index)
1498 cache_left_index = cache[i].next;
1499 if (i == cache_right_index)
1500 cache_right_index = cache[i].prev;
1501 free_slide(i);
1502 return true;
1503 } else
1504 return false;
1508 Read the pfraw image given as filename and return the hid of the buffer
1510 int read_pfraw(char* filename, int prio)
1512 struct pfraw_header bmph;
1513 int fh = rb->open(filename, O_RDONLY);
1514 if( fh < 0 ) {
1515 cache_version = CACHE_UPDATE;
1516 return empty_slide_hid;
1518 else
1519 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1521 int size = sizeof(struct dim) +
1522 sizeof( pix_t ) * bmph.width * bmph.height;
1524 int hid;
1525 do {
1526 hid = rb->buflib_alloc(&buf_ctx, size);
1527 } while (hid < 0 && free_slide_prio(prio));
1529 if (hid < 0) {
1530 rb->close( fh );
1531 return 0;
1534 rb->yield(); /* allow audio to play when fast scrolling */
1535 struct dim *bm = rb->buflib_get_data(&buf_ctx, hid);
1537 bm->width = bmph.width;
1538 bm->height = bmph.height;
1539 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1541 int y;
1542 for( y = 0; y < bm->height; y++ )
1544 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1545 data += bm->width;
1547 rb->close( fh );
1548 return hid;
1553 Load the surface for the given slide_index into the cache at cache_index.
1555 static inline bool load_and_prepare_surface(const int slide_index,
1556 const int cache_index,
1557 const int prio)
1559 char pfraw_file[MAX_PATH];
1560 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%x.pfraw",
1561 mfnv(get_album_name(slide_index)));
1563 int hid = read_pfraw(pfraw_file, prio);
1564 if (!hid)
1565 return false;
1567 cache[cache_index].hid = hid;
1569 if ( cache_index < SLIDE_CACHE_SIZE ) {
1570 cache[cache_index].index = slide_index;
1573 return true;
1578 Load the "next" slide that we can load, freeing old slides if needed, provided
1579 that they are further from center_index than the current slide
1581 bool load_new_slide(void)
1583 int i = -1;
1584 if (cache_center_index != -1)
1586 int next, prev;
1587 if (cache[cache_center_index].index != center_index)
1589 if (cache[cache_center_index].index < center_index)
1591 cache_center_index = seek_right_while(cache_center_index,
1592 cache[next_].index <= center_index);
1593 prev = cache_center_index;
1594 next = cache[cache_center_index].next;
1596 else
1598 cache_center_index = seek_left_while(cache_center_index,
1599 cache[next_].index >= center_index);
1600 next = cache_center_index;
1601 prev = cache[cache_center_index].prev;
1603 if (cache[cache_center_index].index != center_index)
1605 if (cache_free == -1)
1606 free_slide_prio(0);
1607 i = lla_pop_head(&cache_free);
1608 if (!load_and_prepare_surface(center_index, i, 0))
1609 goto fail_and_refree;
1610 if (cache[next].index == -1)
1612 if (cache[prev].index == -1)
1613 goto insert_first_slide;
1614 else
1615 next = cache[prev].next;
1617 lla_insert(i, next);
1618 if (cache[i].index < cache[cache_used].index)
1619 cache_used = i;
1620 cache_center_index = i;
1621 cache_left_index = i;
1622 cache_right_index = i;
1623 return true;
1626 if (cache[cache_left_index].index >
1627 cache[cache_center_index].index)
1628 cache_left_index = cache_center_index;
1629 if (cache[cache_right_index].index <
1630 cache[cache_center_index].index)
1631 cache_right_index = cache_center_index;
1632 cache_left_index = seek_left_while(cache_left_index,
1633 cache[ind_].index - 1 == cache[next_].index);
1634 cache_right_index = seek_right_while(cache_right_index,
1635 cache[ind_].index - 1 == cache[next_].index);
1636 int prio_l = cache[cache_center_index].index -
1637 cache[cache_left_index].index + 1;
1638 int prio_r = cache[cache_right_index].index -
1639 cache[cache_center_index].index + 1;
1640 if ((prio_l < prio_r ||
1641 cache[cache_right_index].index >= number_of_slides) &&
1642 cache[cache_left_index].index > 0)
1644 if (cache_free == -1 && !free_slide_prio(prio_l))
1645 return false;
1646 i = lla_pop_head(&cache_free);
1647 if (load_and_prepare_surface(cache[cache_left_index].index
1648 - 1, i, prio_l))
1650 lla_insert_before(&cache_used, i, cache_left_index);
1651 cache_left_index = i;
1652 return true;
1654 } else if(cache[cache_right_index].index < number_of_slides - 1)
1656 if (cache_free == -1 && !free_slide_prio(prio_r))
1657 return false;
1658 i = lla_pop_head(&cache_free);
1659 if (load_and_prepare_surface(cache[cache_right_index].index
1660 + 1, i, prio_r))
1662 lla_insert_after(i, cache_right_index);
1663 cache_right_index = i;
1664 return true;
1667 } else {
1668 i = lla_pop_head(&cache_free);
1669 if (load_and_prepare_surface(center_index, i, 0))
1671 insert_first_slide:
1672 cache[i].next = i;
1673 cache[i].prev = i;
1674 cache_center_index = i;
1675 cache_left_index = i;
1676 cache_right_index = i;
1677 cache_used = i;
1678 return true;
1681 fail_and_refree:
1682 if (i != -1)
1684 lla_insert_tail(&cache_free, i);
1686 return false;
1691 Get a slide from the buffer
1693 static inline struct dim *get_slide(const int hid)
1695 if (!hid)
1696 return NULL;
1698 struct dim *bmp;
1700 bmp = rb->buflib_get_data(&buf_ctx, hid);
1702 return bmp;
1707 Return the requested surface
1709 static inline struct dim *surface(const int slide_index)
1711 if (slide_index < 0)
1712 return 0;
1713 if (slide_index >= number_of_slides)
1714 return 0;
1715 int i;
1716 if ((i = cache_used ) != -1)
1718 do {
1719 if (cache[i].index == slide_index)
1720 return get_slide(cache[i].hid);
1721 i = cache[i].next;
1722 } while (i != cache_used);
1724 return get_slide(empty_slide_hid);
1728 adjust slides so that they are in "steady state" position
1730 void reset_slides(void)
1732 center_slide.angle = 0;
1733 center_slide.cx = 0;
1734 center_slide.cy = 0;
1735 center_slide.distance = 0;
1736 center_slide.slide_index = center_index;
1738 int i;
1739 for (i = 0; i < num_slides; i++) {
1740 struct slide_data *si = &left_slides[i];
1741 si->angle = itilt;
1742 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1743 si->cy = offsetY;
1744 si->slide_index = center_index - 1 - i;
1745 si->distance = 0;
1748 for (i = 0; i < num_slides; i++) {
1749 struct slide_data *si = &right_slides[i];
1750 si->angle = -itilt;
1751 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1752 si->cy = offsetY;
1753 si->slide_index = center_index + 1 + i;
1754 si->distance = 0;
1760 Updates look-up table and other stuff necessary for the rendering.
1761 Call this when the viewport size or slide dimension is changed.
1763 * To calculate the offset that will provide the proper margin, we use the same
1764 * projection used to render the slides. The solution for xc, the slide center,
1765 * is:
1766 * xp * (zo + xs * sin(r))
1767 * xc = xp - xs * cos(r) + ───────────────────────
1769 * TODO: support moving the side slides toward or away from the camera
1771 void recalc_offsets(void)
1773 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1774 PFreal zo;
1775 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1776 PFREAL_ONE) * zoom / 100;
1777 PFreal cosr, sinr;
1779 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1780 cosr = fcos(-itilt);
1781 sinr = fsin(-itilt);
1782 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1783 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1784 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1785 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1786 / CAM_DIST;
1787 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1792 Fade the given color by spreading the fb_data (ushort)
1793 to an uint, multiply and compress the result back to a ushort.
1795 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1796 static inline unsigned fade_color(pix_t c, unsigned a)
1798 unsigned int result;
1799 c = swap16(c);
1800 a = (a + 2) & 0x1fc;
1801 result = ((c & 0xf81f) * a) & 0xf81f00;
1802 result |= ((c & 0x7e0) * a) & 0x7e000;
1803 result >>= 8;
1804 return swap16(result);
1806 #elif LCD_PIXELFORMAT == RGB565
1807 static inline unsigned fade_color(pix_t c, unsigned a)
1809 unsigned int result;
1810 a = (a + 2) & 0x1fc;
1811 result = ((c & 0xf81f) * a) & 0xf81f00;
1812 result |= ((c & 0x7e0) * a) & 0x7e000;
1813 result >>= 8;
1814 return result;
1816 #else
1817 static inline unsigned fade_color(pix_t c, unsigned a)
1819 unsigned val = c;
1820 return MULUQ(val, a) >> 8;
1822 #endif
1825 * Render a single slide
1826 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1827 * on the slide from its center, zo is the slide's depth offset from the plane
1828 * of the display, r is the angle at which the slide is tilted, and xp is the
1829 * point on the display corresponding to xs on the slide, the projection
1830 * formulas are:
1832 * z * (xc + xs * cos(r))
1833 * xp = ──────────────────────
1834 * z + zo + xs * sin(r)
1836 * z * (xc - xp) - xp * zo
1837 * xs = ────────────────────────
1838 * xp * sin(r) - z * cos(r)
1840 * We use the xp projection once, to find the left edge of the slide on the
1841 * display. From there, we use the xs reverse projection to find the horizontal
1842 * offset from the slide center of each column on the screen, until we reach
1843 * the right edge of the slide, or the screen. The reverse projection can be
1844 * optimized by saving the numerator and denominator of the fraction, which can
1845 * then be incremented by (z + zo) and sin(r) respectively.
1847 void render_slide(struct slide_data *slide, const int alpha)
1849 struct dim *bmp = surface(slide->slide_index);
1850 if (!bmp) {
1851 return;
1853 if (slide->angle > 255 || slide->angle < -255)
1854 return;
1855 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1857 const int sw = bmp->width;
1858 const int sh = bmp->height;
1859 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1860 const int w = LCD_WIDTH;
1862 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1864 if (alpha == 256) { /* opaque -> copy table */
1865 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1866 } else { /* precalculate faded table */
1867 int i, lalpha;
1868 for (i = 0; i < REFLECT_HEIGHT; i++) {
1869 lalpha = reflect_table[i];
1870 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1874 PFreal cosr = fcos(slide->angle);
1875 PFreal sinr = fsin(slide->angle);
1876 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1877 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1878 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1879 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1880 (CAM_DIST_R + zo + fmul(xs,sinr)));
1882 /* Since we're finding the screen position of the left edge of the slide,
1883 * we round up.
1885 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1886 >> PFREAL_SHIFT;
1887 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1888 if (xi >= w) {
1889 return;
1891 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1892 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1893 xs = fdiv(xsnum, xsden);
1895 xsnumi = -CAM_DIST_R - zo;
1896 xsdeni = sinr;
1897 int x;
1898 int dy = PFREAL_ONE;
1899 for (x = xi; x < w; x++) {
1900 int column = (xs - slide_left) / PFREAL_ONE;
1901 if (column >= sw)
1902 break;
1903 if (zo || slide->angle)
1904 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1906 const pix_t *ptr = &src[column * bmp->height];
1908 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
1909 #define PIXELSTEP_Y 1
1910 #define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)])
1911 #else
1912 #define PIXELSTEP_Y BUFFER_WIDTH
1913 #define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
1914 #endif
1916 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1917 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1918 pix_t *pixel = LCDADDR(x, (LCD_HEIGHT/2)-1 );
1920 if (alpha == 256) {
1921 while (p >= plim) {
1922 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1923 p -= dy;
1924 pixel -= PIXELSTEP_Y;
1926 } else {
1927 while (p >= plim) {
1928 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1929 p -= dy;
1930 pixel -= PIXELSTEP_Y;
1933 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1934 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1935 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1936 p + (LCD_HEIGHT/2) * dy);
1937 pixel = LCDADDR(x, (LCD_HEIGHT/2) );
1939 if (alpha == 256) {
1940 while (p < plim) {
1941 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1942 p += dy;
1943 pixel += PIXELSTEP_Y;
1945 } else {
1946 while (p < plim) {
1947 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1948 p += dy;
1949 pixel += PIXELSTEP_Y;
1952 while (p < plim2) {
1953 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1954 int lalpha = reftab[ty];
1955 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1956 p += dy;
1957 pixel += PIXELSTEP_Y;
1960 if (zo || slide->angle)
1962 xsnum += xsnumi;
1963 xsden += xsdeni;
1964 xs = fdiv(xsnum, xsden);
1965 } else
1966 xs += PFREAL_ONE;
1969 /* let the music play... */
1970 rb->yield();
1971 return;
1975 Jump the the given slide_index
1977 static inline void set_current_slide(const int slide_index)
1979 int old_center_index = center_index;
1980 step = 0;
1981 center_index = fbound(slide_index, 0, number_of_slides - 1);
1982 if (old_center_index != center_index)
1983 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1984 target = center_index;
1985 slide_frame = slide_index << 16;
1986 reset_slides();
1990 Start the animation for changing slides
1992 void start_animation(void)
1994 step = (target < center_slide.slide_index) ? -1 : 1;
1995 pf_state = pf_scrolling;
1999 Go to the previous slide
2001 void show_previous_slide(void)
2003 if (step == 0) {
2004 if (center_index > 0) {
2005 target = center_index - 1;
2006 start_animation();
2008 } else if ( step > 0 ) {
2009 target = center_index;
2010 step = (target <= center_slide.slide_index) ? -1 : 1;
2011 } else {
2012 target = fmax(0, center_index - 2);
2018 Go to the next slide
2020 void show_next_slide(void)
2022 if (step == 0) {
2023 if (center_index < number_of_slides - 1) {
2024 target = center_index + 1;
2025 start_animation();
2027 } else if ( step < 0 ) {
2028 target = center_index;
2029 step = (target < center_slide.slide_index) ? -1 : 1;
2030 } else {
2031 target = fmin(center_index + 2, number_of_slides - 1);
2037 Render the slides. Updates only the offscreen buffer.
2039 void render_all_slides(void)
2041 mylcd_set_background(G_BRIGHT(0));
2042 /* TODO: Optimizes this by e.g. invalidating rects */
2043 mylcd_clear_display();
2045 int nleft = num_slides;
2046 int nright = num_slides;
2048 int alpha;
2049 int index;
2050 if (step == 0) {
2051 /* no animation, boring plain rendering */
2052 for (index = nleft - 2; index >= 0; index--) {
2053 alpha = (index < nleft - 2) ? 256 : 128;
2054 alpha -= extra_fade;
2055 if (alpha > 0 )
2056 render_slide(&left_slides[index], alpha);
2058 for (index = nright - 2; index >= 0; index--) {
2059 alpha = (index < nright - 2) ? 256 : 128;
2060 alpha -= extra_fade;
2061 if (alpha > 0 )
2062 render_slide(&right_slides[index], alpha);
2064 } else {
2065 /* the first and last slide must fade in/fade out */
2067 /* if step<0 and nleft==1, left_slides[0] is fading in */
2068 alpha = ((step > 0) ? 0 : ((nleft == 1) ? 256 : 128)) - fade / 2;
2069 for (index = nleft - 1; index >= 0; index--) {
2070 if (alpha > 0)
2071 render_slide(&left_slides[index], alpha);
2072 alpha += 128;
2073 if (alpha > 256) alpha = 256;
2075 /* if step>0 and nright==1, right_slides[0] is fading in */
2076 alpha = ((step > 0) ? ((nright == 1) ? 128 : 0) : -128) + fade / 2;
2077 for (index = nright - 1; index >= 0; index--) {
2078 if (alpha > 0)
2079 render_slide(&right_slides[index], alpha);
2080 alpha += 128;
2081 if (alpha > 256) alpha = 256;
2084 alpha = 256;
2085 if (step != 0 && num_slides <= 2) /* fading out center slide */
2086 alpha = (step > 0) ? 256 - fade / 2 : 128 + fade / 2;
2087 render_slide(&center_slide, alpha);
2092 Updates the animation effect. Call this periodically from a timer.
2094 void update_scroll_animation(void)
2096 if (step == 0)
2097 return;
2099 int speed = 16384;
2100 int i;
2102 /* deaccelerate when approaching the target */
2103 if (true) {
2104 const int max = 2 * 65536;
2106 int fi = slide_frame;
2107 fi -= (target << 16);
2108 if (fi < 0)
2109 fi = -fi;
2110 fi = fmin(fi, max);
2112 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
2113 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
2116 slide_frame += speed * step;
2118 int index = slide_frame >> 16;
2119 int pos = slide_frame & 0xffff;
2120 int neg = 65536 - pos;
2121 int tick = (step < 0) ? neg : pos;
2122 PFreal ftick = (tick * PFREAL_ONE) >> 16;
2124 /* the leftmost and rightmost slide must fade away */
2125 fade = pos / 256;
2127 if (step < 0)
2128 index++;
2129 if (center_index != index) {
2130 center_index = index;
2131 rb->queue_post(&thread_q, EV_WAKEUP, 0);
2132 slide_frame = index << 16;
2133 center_slide.slide_index = center_index;
2134 for (i = 0; i < num_slides; i++)
2135 left_slides[i].slide_index = center_index - 1 - i;
2136 for (i = 0; i < num_slides; i++)
2137 right_slides[i].slide_index = center_index + 1 + i;
2140 center_slide.angle = (step * tick * itilt) >> 16;
2141 center_slide.cx = -step * fmul(offsetX, ftick);
2142 center_slide.cy = fmul(offsetY, ftick);
2144 if (center_index == target) {
2145 reset_slides();
2146 pf_state = pf_idle;
2147 slide_frame = center_index << 16;
2148 step = 0;
2149 fade = 256;
2150 return;
2153 for (i = 0; i < num_slides; i++) {
2154 struct slide_data *si = &left_slides[i];
2155 si->angle = itilt;
2156 si->cx =
2157 -(offsetX + slide_spacing * i * PFREAL_ONE + step
2158 * slide_spacing * ftick);
2159 si->cy = offsetY;
2162 for (i = 0; i < num_slides; i++) {
2163 struct slide_data *si = &right_slides[i];
2164 si->angle = -itilt;
2165 si->cx =
2166 offsetX + slide_spacing * i * PFREAL_ONE - step
2167 * slide_spacing * ftick;
2168 si->cy = offsetY;
2171 if (step > 0) {
2172 PFreal ftick = (neg * PFREAL_ONE) >> 16;
2173 right_slides[0].angle = -(neg * itilt) >> 16;
2174 right_slides[0].cx = fmul(offsetX, ftick);
2175 right_slides[0].cy = fmul(offsetY, ftick);
2176 } else {
2177 PFreal ftick = (pos * PFREAL_ONE) >> 16;
2178 left_slides[0].angle = (pos * itilt) >> 16;
2179 left_slides[0].cx = -fmul(offsetX, ftick);
2180 left_slides[0].cy = fmul(offsetY, ftick);
2183 /* must change direction ? */
2184 if (target < index)
2185 if (step > 0)
2186 step = -1;
2187 if (target > index)
2188 if (step < 0)
2189 step = 1;
2194 Cleanup the plugin
2196 void cleanup(void)
2198 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2199 rb->cpu_boost(false);
2200 #endif
2201 end_pf_thread();
2202 /* Turn on backlight timeout (revert to settings) */
2203 backlight_use_settings();
2205 #ifdef USEGSLIB
2206 grey_release();
2207 #endif
2211 Shows the settings menu
2213 int settings_menu(void)
2215 int selection = 0;
2216 bool old_val;
2218 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2219 "Spacing", "Centre margin", "Number of slides", "Zoom",
2220 "Show album title", "Resize Covers", "Rebuild cache",
2221 "WPS Integration", "Backlight");
2223 static const struct opt_items album_name_options[] = {
2224 { "Hide album title", -1 },
2225 { "Show at the bottom", -1 },
2226 { "Show at the top", -1 }
2228 static const struct opt_items wps_options[] = {
2229 { "Off", -1 },
2230 { "Direct", -1 },
2231 { "Via Track list", -1 }
2233 static const struct opt_items backlight_options[] = {
2234 { "Always On", -1 },
2235 { "Normal", -1 },
2238 do {
2239 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2240 switch(selection) {
2241 case 0:
2242 rb->set_bool("Show FPS", &show_fps);
2243 reset_track_list();
2244 break;
2246 case 1:
2247 rb->set_int("Spacing between slides", "", 1,
2248 &slide_spacing,
2249 NULL, 1, 0, 100, NULL );
2250 recalc_offsets();
2251 reset_slides();
2252 break;
2254 case 2:
2255 rb->set_int("Centre margin", "", 1,
2256 &center_margin,
2257 NULL, 1, 0, 80, NULL );
2258 recalc_offsets();
2259 reset_slides();
2260 break;
2262 case 3:
2263 rb->set_int("Number of slides", "", 1, &num_slides,
2264 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2265 recalc_offsets();
2266 reset_slides();
2267 break;
2269 case 4:
2270 rb->set_int("Zoom", "", 1, &zoom,
2271 NULL, 1, 10, 300, NULL );
2272 recalc_offsets();
2273 reset_slides();
2274 break;
2275 case 5:
2276 rb->set_option("Show album title", &show_album_name,
2277 INT, album_name_options, 3, NULL);
2278 reset_track_list();
2279 recalc_offsets();
2280 reset_slides();
2281 break;
2282 case 6:
2283 old_val = resize;
2284 rb->set_bool("Resize Covers", &resize);
2285 if (old_val == resize) /* changed? */
2286 break;
2287 /* fallthrough if changed, since cache needs to be rebuilt */
2288 case 7:
2289 cache_version = CACHE_REBUILD;
2290 rb->remove(EMPTY_SLIDE);
2291 configfile_save(CONFIG_FILE, config,
2292 CONFIG_NUM_ITEMS, CONFIG_VERSION);
2293 rb->splash(HZ, "Cache will be rebuilt on next restart");
2294 break;
2295 case 8:
2296 rb->set_option("WPS Integration", &auto_wps, INT, wps_options, 3, NULL);
2297 break;
2298 case 9:
2299 rb->set_option("Backlight", &backlight_mode, INT, backlight_options, 2, NULL);
2300 break;
2302 case MENU_ATTACHED_USB:
2303 return PLUGIN_USB_CONNECTED;
2305 } while ( selection >= 0 );
2306 return 0;
2310 Show the main menu
2312 enum {
2313 PF_GOTO_WPS,
2314 #if PF_PLAYBACK_CAPABLE
2315 PF_MENU_CLEAR_PLAYLIST,
2316 PF_MENU_PLAYBACK_CONTROL,
2317 #endif
2318 PF_MENU_SETTINGS,
2319 PF_MENU_RETURN,
2320 PF_MENU_QUIT,
2323 int main_menu(void)
2325 int selection = 0;
2326 int result;
2328 #if LCD_DEPTH > 1
2329 rb->lcd_set_foreground(N_BRIGHT(255));
2330 #endif
2332 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2333 "Go to WPS",
2334 #if PF_PLAYBACK_CAPABLE
2335 "Clear playlist", "Playback Control",
2336 #endif
2337 "Settings", "Return", "Quit");
2338 while (1) {
2339 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2340 case PF_GOTO_WPS: /* WPS */
2341 return -2;
2342 #if PF_PLAYBACK_CAPABLE
2343 case PF_MENU_CLEAR_PLAYLIST:
2344 if(rb->playlist_remove_all_tracks(NULL) == 0) {
2345 rb->playlist_create(NULL, NULL);
2346 rb->splash(HZ*2, "Playlist Cleared");
2348 break;
2349 case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */
2350 playback_control(NULL);
2351 break;
2352 #endif
2353 case PF_MENU_SETTINGS:
2354 result = settings_menu();
2355 if ( result != 0 ) return result;
2356 break;
2357 case PF_MENU_RETURN:
2358 return 0;
2359 case PF_MENU_QUIT:
2360 return -1;
2362 case MENU_ATTACHED_USB:
2363 return PLUGIN_USB_CONNECTED;
2365 default:
2366 return 0;
2372 Animation step for zooming into the current cover
2374 void update_cover_in_animation(void)
2376 cover_animation_keyframe++;
2377 if( cover_animation_keyframe < 20 ) {
2378 center_slide.distance-=5;
2379 center_slide.angle+=1;
2380 extra_fade += 13;
2382 else if( cover_animation_keyframe < 35 ) {
2383 center_slide.angle+=16;
2385 else {
2386 cover_animation_keyframe = 0;
2387 pf_state = pf_show_tracks;
2392 Animation step for zooming out the current cover
2394 void update_cover_out_animation(void)
2396 cover_animation_keyframe++;
2397 if( cover_animation_keyframe <= 15 ) {
2398 center_slide.angle-=16;
2400 else if( cover_animation_keyframe < 35 ) {
2401 center_slide.distance+=5;
2402 center_slide.angle-=1;
2403 extra_fade -= 13;
2405 else {
2406 cover_animation_keyframe = 0;
2407 pf_state = pf_idle;
2412 Draw a blue gradient at y with height h
2414 static inline void draw_gradient(int y, int h)
2416 int r, inc, c;
2417 inc = (100 << 8) / h;
2418 c = 0;
2419 selected_track_pulse = (selected_track_pulse+1) % 10;
2420 int c2 = selected_track_pulse - 5;
2421 for (r=0; r<h; r++) {
2422 #ifdef HAVE_LCD_COLOR
2423 mylcd_set_foreground(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2424 c2+250-(c >> 8)));
2425 #else
2426 mylcd_set_foreground(G_BRIGHT(c2+160-(c >> 8)));
2427 #endif
2428 mylcd_hline(0, LCD_WIDTH, r+y);
2429 if ( r > h/2 )
2430 c-=inc;
2431 else
2432 c+=inc;
2437 static void track_list_yh(int char_height)
2439 switch (show_album_name)
2441 case ALBUM_NAME_HIDE:
2442 track_list_y = (show_fps ? char_height : 0);
2443 track_list_h = LCD_HEIGHT - track_list_y;
2444 break;
2445 case ALBUM_NAME_BOTTOM:
2446 track_list_y = (show_fps ? char_height : 0);
2447 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2448 break;
2449 case ALBUM_NAME_TOP:
2450 default:
2451 track_list_y = char_height * 2;
2452 track_list_h = LCD_HEIGHT - track_list_y -
2453 (show_fps ? char_height : 0);
2454 break;
2459 Reset the track list after a album change
2461 void reset_track_list(void)
2463 int char_height = rb->screens[SCREEN_MAIN]->getcharheight();
2464 int total_height;
2465 track_list_yh(char_height);
2466 track_list_visible_entries = fmin( track_list_h/char_height , track_count );
2467 start_index_track_list = 0;
2468 selected_track = 0;
2469 last_selected_track = -1;
2471 /* let the tracklist start more centered
2472 * if the screen isn't filled with tracks */
2473 total_height = track_count*char_height;
2474 if (total_height < track_list_h)
2476 track_list_y += (track_list_h - total_height) / 2;
2477 track_list_h = total_height;
2482 Display the list of tracks
2484 void show_track_list(void)
2486 mylcd_clear_display();
2487 if ( center_slide.slide_index != track_index ) {
2488 create_track_index(center_slide.slide_index);
2489 reset_track_list();
2491 int titletxt_w, titletxt_x, color, titletxt_h;
2492 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2494 int titletxt_y = track_list_y;
2495 int track_i;
2496 track_i = start_index_track_list;
2497 for (;track_i < track_list_visible_entries+start_index_track_list;
2498 track_i++)
2500 char *trackname = get_track_name(track_i);
2501 if ( track_i == selected_track ) {
2502 if (selected_track != last_selected_track) {
2503 set_scroll_line(trackname, PF_SCROLL_TRACK);
2504 last_selected_track = selected_track;
2506 draw_gradient(titletxt_y, titletxt_h);
2507 titletxt_x = get_scroll_line_offset(PF_SCROLL_TRACK);
2508 color = 255;
2510 else {
2511 titletxt_w = mylcd_getstringsize(trackname, NULL, NULL);
2512 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2513 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2515 mylcd_set_foreground(G_BRIGHT(color));
2516 mylcd_putsxy(titletxt_x,titletxt_y,trackname);
2517 titletxt_y += titletxt_h;
2521 void select_next_track(void)
2523 if ( selected_track < track_count - 1 ) {
2524 selected_track++;
2525 if (selected_track==(track_list_visible_entries+start_index_track_list))
2526 start_index_track_list++;
2530 void select_prev_track(void)
2532 if (selected_track > 0 ) {
2533 if (selected_track==start_index_track_list) start_index_track_list--;
2534 selected_track--;
2538 #if PF_PLAYBACK_CAPABLE
2540 * Puts the current tracklist into a newly created playlist and starts playling
2542 void start_playback(bool append)
2544 static int old_playlist = -1, old_shuffle = 0;
2545 int count = 0;
2546 int position = selected_track;
2547 int shuffle = rb->global_settings->playlist_shuffle;
2548 /* reuse existing playlist if possible
2549 * regenerate if shuffle is on or changed, since playlist index and
2550 * selected track are "out of sync" */
2551 if (!shuffle && !append && center_slide.slide_index == old_playlist
2552 && (old_shuffle == shuffle))
2554 goto play;
2556 /* First, replace the current playlist with a new one */
2557 else if (append || (rb->playlist_remove_all_tracks(NULL) == 0
2558 && rb->playlist_create(NULL, NULL) == 0))
2560 do {
2561 rb->yield();
2562 if (rb->playlist_insert_track(NULL, get_track_filename(count),
2563 PLAYLIST_INSERT_LAST, false, true) < 0)
2564 break;
2565 } while(++count < track_count);
2566 rb->playlist_sync(NULL);
2568 else
2569 return;
2571 if (rb->global_settings->playlist_shuffle)
2572 position = rb->playlist_shuffle(*rb->current_tick, selected_track);
2573 play:
2574 /* TODO: can we adjust selected_track if !play_selected ?
2575 * if shuffle, we can't predict the playing track easily, and for either
2576 * case the track list doesn't get auto scrolled*/
2577 if(!append)
2578 rb->playlist_start(position, 0);
2579 old_playlist = center_slide.slide_index;
2580 old_shuffle = shuffle;
2582 #endif
2585 Draw the current album name
2587 void draw_album_text(void)
2589 if (show_album_name == ALBUM_NAME_HIDE)
2590 return;
2592 int albumtxt_index;
2593 int char_height;
2594 int albumtxt_x, albumtxt_y;
2596 char *albumtxt;
2597 int c;
2598 /* Draw album text */
2599 if ( pf_state == pf_scrolling ) {
2600 c = ((slide_frame & 0xffff )/ 255);
2601 if (step < 0) c = 255-c;
2602 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2603 albumtxt_index = center_index+step;
2604 c = (c-128)*2;
2606 else {
2607 albumtxt_index = center_index;
2608 c = (128-c)*2;
2611 else {
2612 albumtxt_index = center_index;
2613 c= 255;
2615 albumtxt = get_album_name(albumtxt_index);
2617 mylcd_set_foreground(G_BRIGHT(c));
2618 if (albumtxt_index != prev_albumtxt_index) {
2619 set_scroll_line(albumtxt, PF_SCROLL_ALBUM);
2620 prev_albumtxt_index = albumtxt_index;
2623 char_height = rb->screens[SCREEN_MAIN]->getcharheight();
2624 if (show_album_name == ALBUM_NAME_TOP)
2625 albumtxt_y = char_height / 2;
2626 else
2627 albumtxt_y = LCD_HEIGHT - char_height - char_height/2;
2629 albumtxt_x = get_scroll_line_offset(PF_SCROLL_ALBUM);
2630 mylcd_putsxy(albumtxt_x, albumtxt_y, albumtxt);
2634 Display an error message and wait for input.
2636 void error_wait(const char *message)
2638 rb->splashf(0, "%s. Press any button to continue.", message);
2639 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2640 rb->yield();
2641 rb->sleep(2 * HZ);
2645 Main function that also contain the main plasma
2646 algorithm.
2648 int main(void)
2650 int ret;
2652 rb->lcd_setfont(FONT_UI);
2654 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2655 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2656 error_wait("Could not create directory " CACHE_PREFIX);
2657 return PLUGIN_ERROR;
2661 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2662 if(auto_wps == 0)
2663 draw_splashscreen();
2664 if(backlight_mode == 0) {
2665 /* Turn off backlight timeout */
2666 backlight_ignore_timeout();
2669 init_scroll_lines();
2670 init_reflect_table();
2672 ALIGN_BUFFER(buf, buf_size, 4);
2673 ret = create_album_index();
2674 if (ret == ERROR_BUFFER_FULL) {
2675 error_wait("Not enough memory for album names");
2676 return PLUGIN_ERROR;
2677 } else if (ret == ERROR_NO_ALBUMS) {
2678 error_wait("No albums found. Please enable database");
2679 return PLUGIN_ERROR;
2682 ALIGN_BUFFER(buf, buf_size, 4);
2683 number_of_slides = album_count;
2684 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2685 cache_version = CACHE_REBUILD;
2686 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2687 error_wait("Could not create album art cache");
2688 return PLUGIN_ERROR;
2691 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2692 cache_version = CACHE_REBUILD;
2693 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2694 error_wait("Could not load the empty slide");
2695 return PLUGIN_ERROR;
2697 if (cache_version != CACHE_VERSION)
2699 cache_version = CACHE_VERSION;
2700 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2703 rb->buflib_init(&buf_ctx, (void *)buf, buf_size);
2705 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2707 error_wait("Unable to load empty slide image");
2708 return PLUGIN_ERROR;
2711 if (!create_pf_thread()) {
2712 error_wait("Cannot create thread!");
2713 return PLUGIN_ERROR;
2716 int i;
2718 /* initialize */
2719 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2720 cache[i].hid = 0;
2721 cache[i].index = 0;
2722 cache[i].next = i + 1;
2723 cache[i].prev = i - 1;
2725 cache[0].prev = i - 1;
2726 cache[i - 1].next = 0;
2727 cache_free = 0;
2728 buffer = LCD_BUF;
2730 pf_state = pf_idle;
2732 track_index = -1;
2733 extra_fade = 0;
2734 slide_frame = 0;
2735 step = 0;
2736 target = 0;
2737 fade = 256;
2739 recalc_offsets();
2740 reset_slides();
2741 set_current_slide(get_wps_current_index());
2743 char fpstxt[10];
2744 int button;
2746 int frames = 0;
2747 long last_update = *rb->current_tick;
2748 long current_update;
2749 long update_interval = 100;
2750 int fps = 0;
2751 int fpstxt_y;
2753 bool instant_update;
2754 #ifdef USEGSLIB
2755 grey_show(true);
2756 grey_set_drawmode(DRMODE_FG);
2757 #endif
2758 rb->lcd_set_drawmode(DRMODE_FG);
2759 while (true) {
2760 current_update = *rb->current_tick;
2761 frames++;
2763 /* Initial rendering */
2764 instant_update = false;
2766 update_scroll_lines();
2768 /* Handle states */
2769 switch ( pf_state ) {
2770 case pf_scrolling:
2771 update_scroll_animation();
2772 render_all_slides();
2773 instant_update = true;
2774 break;
2775 case pf_cover_in:
2776 update_cover_in_animation();
2777 render_all_slides();
2778 instant_update = true;
2779 break;
2780 case pf_cover_out:
2781 update_cover_out_animation();
2782 render_all_slides();
2783 instant_update = true;
2784 break;
2785 case pf_show_tracks:
2786 show_track_list();
2787 break;
2788 case pf_idle:
2789 render_all_slides();
2790 break;
2793 /* Calculate FPS */
2794 if (current_update - last_update > update_interval) {
2795 fps = frames * HZ / (current_update - last_update);
2796 last_update = current_update;
2797 frames = 0;
2799 /* Draw FPS */
2800 if (show_fps)
2802 #ifdef USEGSLIB
2803 mylcd_set_foreground(G_BRIGHT(255));
2804 #else
2805 mylcd_set_foreground(G_PIX(255,0,0));
2806 #endif
2807 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2808 if (show_album_name == ALBUM_NAME_TOP)
2809 fpstxt_y = LCD_HEIGHT -
2810 rb->screens[SCREEN_MAIN]->getcharheight();
2811 else
2812 fpstxt_y = 0;
2813 mylcd_putsxy(0, fpstxt_y, fpstxt);
2815 draw_album_text();
2818 /* Copy offscreen buffer to LCD and give time to other threads */
2819 mylcd_update();
2820 rb->yield();
2822 /*/ Handle buttons */
2823 button = rb->get_custom_action(CONTEXT_PLUGIN
2824 #ifndef USE_CORE_PREVNEXT
2825 |(pf_state == pf_show_tracks ? 1 : 0)
2826 #endif
2827 ,instant_update ? 0 : HZ/16,
2828 get_context_map);
2830 switch (button) {
2831 case PF_QUIT:
2832 return PLUGIN_OK;
2833 case PF_WPS:
2834 return PLUGIN_GOTO_WPS;
2835 case PF_BACK:
2836 if ( pf_state == pf_show_tracks )
2838 rb->buflib_buffer_in(&buf_ctx, borrowed);
2839 borrowed = 0;
2840 track_index = -1;
2841 pf_state = pf_cover_out;
2843 if (pf_state == pf_idle || pf_state == pf_scrolling)
2844 return PLUGIN_OK;
2845 break;
2846 case PF_MENU:
2847 #ifdef USEGSLIB
2848 grey_show(false);
2849 #endif
2850 ret = main_menu();
2851 if ( ret == -2 ) return PLUGIN_GOTO_WPS;
2852 if ( ret == -1 ) return PLUGIN_OK;
2853 if ( ret != 0 ) return ret;
2854 #ifdef USEGSLIB
2855 grey_show(true);
2856 #endif
2857 mylcd_set_drawmode(DRMODE_FG);
2858 break;
2860 case PF_NEXT:
2861 case PF_NEXT_REPEAT:
2862 if ( pf_state == pf_show_tracks )
2863 select_next_track();
2864 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2865 show_next_slide();
2866 break;
2868 case PF_PREV:
2869 case PF_PREV_REPEAT:
2870 if ( pf_state == pf_show_tracks )
2871 select_prev_track();
2872 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2873 show_previous_slide();
2874 break;
2875 #if PF_PLAYBACK_CAPABLE
2876 case PF_CONTEXT:
2877 if ( auto_wps != 0 ) {
2878 if( pf_state == pf_idle ) {
2879 create_track_index(center_slide.slide_index);
2880 reset_track_list();
2881 start_playback(true);
2882 rb->splash(HZ*2, "Added to playlist");
2884 else if( pf_state == pf_show_tracks ) {
2885 rb->playlist_insert_track(NULL, get_track_filename(selected_track),
2886 PLAYLIST_INSERT_LAST, false, true);
2887 rb->playlist_sync(NULL);
2888 rb->splash(HZ*2, "Added to playlist");
2891 break;
2892 #endif
2893 case PF_TRACKLIST:
2894 if ( auto_wps == 1 && pf_state == pf_idle ) {
2895 pf_state = pf_cover_in;
2896 break;
2898 case PF_SELECT:
2899 if ( pf_state == pf_idle ) {
2900 #if PF_PLAYBACK_CAPABLE
2901 if(auto_wps == 1) {
2902 create_track_index(center_slide.slide_index);
2903 reset_track_list();
2904 start_playback(false);
2905 last_album = center_index;
2906 return PLUGIN_GOTO_WPS;
2908 else
2909 #endif
2910 pf_state = pf_cover_in;
2912 else if ( pf_state == pf_show_tracks ) {
2913 #if PF_PLAYBACK_CAPABLE
2914 start_playback(false);
2915 if(auto_wps != 0) {
2916 last_album = center_index;
2917 return PLUGIN_GOTO_WPS;
2919 #endif
2921 break;
2922 default:
2923 exit_on_usb(button);
2924 break;
2929 /*************************** Plugin entry point ****************************/
2931 enum plugin_status plugin_start(const void *parameter)
2933 int ret;
2934 (void) parameter;
2935 atexit(cleanup);
2937 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2938 rb->cpu_boost(true);
2939 #endif
2940 #if PF_PLAYBACK_CAPABLE
2941 buf = rb->plugin_get_buffer(&buf_size);
2942 #else
2943 buf = rb->plugin_get_audio_buffer(&buf_size);
2944 #ifndef SIMULATOR
2945 if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
2947 uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
2948 buf_size = MIN(buf_size, tmp_size);
2950 #endif
2951 #endif
2953 #ifdef USEGSLIB
2954 long grey_buf_used;
2955 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2956 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2958 error_wait("Greylib init failed!");
2959 return PLUGIN_ERROR;
2961 grey_setfont(FONT_UI);
2962 buf_size -= grey_buf_used;
2963 buf = (void*)(grey_buf_used + (char*)buf);
2964 #endif
2966 ret = main();
2967 if ( ret == PLUGIN_OK || ret == PLUGIN_GOTO_WPS) {
2968 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2969 CONFIG_VERSION))
2971 rb->splash(HZ, "Error writing config.");
2972 ret = PLUGIN_ERROR;
2975 return ret;