FS#12076 - DB stats resurrection: If the filename was changed, require
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blobd26e6ead601393689fc4801b0c75a9d467542878
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"
36 #include "lib/buflib.h"
40 /******************************* Globals ***********************************/
43 * Targets which use plugin_get_audio_buffer() can't have playback from
44 * within pictureflow itself, as the whole core audio buffer is occupied */
45 #define PF_PLAYBACK_CAPABLE (PLUGIN_BUFFER_SIZE > 0x10000)
47 #if PF_PLAYBACK_CAPABLE
48 #include "lib/playback_control.h"
49 #endif
51 #define PF_PREV ACTION_STD_PREV
52 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
53 #define PF_NEXT ACTION_STD_NEXT
54 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
55 #define PF_SELECT ACTION_STD_OK
56 #define PF_CONTEXT ACTION_STD_CONTEXT
57 #define PF_BACK ACTION_STD_CANCEL
58 #define PF_MENU ACTION_STD_MENU
59 #define PF_WPS ACTION_TREE_WPS
61 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
62 #define PF_TRACKLIST (LAST_ACTION_PLACEHOLDER + 2)
64 #if defined(HAVE_SCROLLWHEEL) || CONFIG_KEYPAD == IRIVER_H10_PAD || \
65 CONFIG_KEYPAD == SAMSUNG_YH_PAD
66 #define USE_CORE_PREVNEXT
67 #endif
69 #ifndef USE_CORE_PREVNEXT
70 /* scrollwheel targets use the wheel, just as they do in lists,
71 * so there's no need for a special context,
72 * others use left/right here too (as oppsed to up/down in lists) */
73 const struct button_mapping pf_context_album_scroll[] =
75 #ifdef HAVE_TOUCHSCREEN
76 {PF_PREV, BUTTON_MIDLEFT, BUTTON_NONE},
77 {PF_PREV_REPEAT, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE},
78 {PF_NEXT, BUTTON_MIDRIGHT, BUTTON_NONE},
79 {PF_NEXT_REPEAT, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE},
80 #endif
81 #if (CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD)
82 {PF_PREV, BUTTON_RC_REW, BUTTON_NONE},
83 {PF_PREV_REPEAT, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE},
84 {PF_NEXT, BUTTON_RC_FF, BUTTON_NONE},
85 {PF_NEXT_REPEAT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE},
86 #else
87 {PF_PREV, BUTTON_LEFT, BUTTON_NONE},
88 {PF_PREV_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE},
89 {PF_NEXT, BUTTON_RIGHT, BUTTON_NONE},
90 {PF_NEXT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE},
91 {ACTION_NONE, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT},
92 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT},
93 {ACTION_NONE, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_LEFT},
94 {ACTION_NONE, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_RIGHT},
95 #endif
96 #if CONFIG_KEYPAD == ONDIO_PAD
97 {PF_SELECT, BUTTON_UP|BUTTON_REL, BUTTON_UP},
98 {PF_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP},
99 {ACTION_NONE, BUTTON_UP, BUTTON_NONE},
100 {ACTION_NONE, BUTTON_DOWN, BUTTON_NONE},
101 {ACTION_NONE, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE},
102 #endif
103 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_PLUGIN|1)
105 #endif /* !USE_CORE_PREVNEXT */
107 const struct button_mapping pf_context_buttons[] =
109 #ifdef HAVE_TOUCHSCREEN
110 {PF_SELECT, BUTTON_CENTER, BUTTON_NONE},
111 {PF_BACK, BUTTON_BOTTOMRIGHT, BUTTON_NONE},
112 #endif
113 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
114 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
115 #elif CONFIG_KEYPAD == SANSA_C100_PAD
116 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
117 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
118 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
119 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
120 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
121 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
122 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
123 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
124 {PF_QUIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE},
125 {PF_TRACKLIST, BUTTON_RIGHT, BUTTON_NONE},
126 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
128 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
129 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWON_D2_PAD
130 {PF_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER},
131 #if CONFIG_KEYPAD == COWON_D2_PAD
132 {PF_BACK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER},
133 {ACTION_NONE, BUTTON_POWER, BUTTON_NONE},
134 #endif
135 #elif CONFIG_KEYPAD == SANSA_E200_PAD
136 {PF_QUIT, BUTTON_POWER, BUTTON_NONE},
137 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
138 {PF_QUIT, BUTTON_EQ, BUTTON_NONE},
139 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
140 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
141 || (CONFIG_KEYPAD == IPOD_4G_PAD)
142 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
143 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
144 {PF_QUIT, BUTTON_POWERPLAY|BUTTON_REPEAT, BUTTON_POWERPLAY},
145 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
146 {PF_QUIT, BUTTON_RC_REC, BUTTON_NONE},
147 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
148 {PF_QUIT, BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU},
149 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
150 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
151 {PF_QUIT, BUTTON_OFF, BUTTON_NONE},
152 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
153 {PF_QUIT, BUTTON_REC, BUTTON_NONE},
154 #endif
155 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
156 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD|CONTEXT_REMOTE)
157 #else
158 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE)
159 #endif
161 const struct button_mapping *pf_contexts[] =
163 #ifndef USE_CORE_PREVNEXT
164 pf_context_album_scroll,
165 #endif
166 pf_context_buttons
169 #if LCD_DEPTH < 8
170 #if LCD_DEPTH > 1
171 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
172 #else /* LCD_DEPTH <= 1 */
173 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
174 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
175 #define PICTUREFLOW_DRMODE DRMODE_SOLID
176 #else
177 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
178 #endif
179 #endif /* LCD_DEPTH <= 1 */
180 #define USEGSLIB
181 GREY_INFO_STRUCT
182 #define LCD_BUF _grey_info.buffer
183 #define G_PIX(r,g,b) \
184 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
185 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
186 #define G_BRIGHT(y) (y)
187 #define BUFFER_WIDTH _grey_info.width
188 #define BUFFER_HEIGHT _grey_info.height
189 typedef unsigned char pix_t;
190 #else /* LCD_DEPTH >= 8 */
191 #define LCD_BUF rb->lcd_framebuffer
192 #define G_PIX LCD_RGBPACK
193 #define N_PIX LCD_RGBPACK
194 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
195 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
196 #define BUFFER_WIDTH LCD_WIDTH
197 #define BUFFER_HEIGHT LCD_HEIGHT
198 typedef fb_data pix_t;
199 #endif /* LCD_DEPTH >= 8 */
201 /* for fixed-point arithmetic, we need minimum 32-bit long
202 long long (64-bit) might be useful for multiplication and division */
203 #define PFreal long
204 #define PFREAL_SHIFT 10
205 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
206 #define PFREAL_ONE (1 << PFREAL_SHIFT)
207 #define PFREAL_HALF (PFREAL_ONE >> 1)
210 #define IANGLE_MAX 1024
211 #define IANGLE_MASK 1023
213 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
214 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
215 #define DISPLAY_HEIGHT REFLECT_TOP
216 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
217 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
218 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
219 (REFLECT_HEIGHT * 5))
220 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
221 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
222 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
223 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
224 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
226 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
228 #define MAX_SLIDES_COUNT 10
230 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
231 #define CACHE_PREFIX PLUGIN_DEMOS_DATA_DIR "/pictureflow"
233 #define EV_EXIT 9999
234 #define EV_WAKEUP 1337
236 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
237 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
238 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
240 /* some magic numbers for cache_version. */
241 #define CACHE_REBUILD 0
242 #define CACHE_UPDATE 1
244 /* Error return values */
245 #define ERROR_NO_ALBUMS -1
246 #define ERROR_BUFFER_FULL -2
248 /* current version for cover cache */
249 #define CACHE_VERSION 3
250 #define CONFIG_VERSION 1
251 #define CONFIG_FILE "pictureflow.cfg"
253 /** structs we use */
255 struct slide_data {
256 int slide_index;
257 int angle;
258 PFreal cx;
259 PFreal cy;
260 PFreal distance;
263 struct slide_cache {
264 int index; /* index of the cached slide */
265 int hid; /* handle ID of the cached slide */
266 short next; /* "next" slide, with LRU last */
267 short prev; /* "previous" slide */
270 struct album_data {
271 int name_idx;
272 long seek;
275 struct track_data {
276 uint32_t sort;
277 int name_idx; /* offset to the track name */
278 long seek;
279 #if PF_PLAYBACK_CAPABLE
280 /* offset to the filename in the string, needed for playlist generation */
281 int filename_idx;
282 #endif
285 struct rect {
286 int left;
287 int right;
288 int top;
289 int bottom;
292 struct load_slide_event_data {
293 int slide_index;
294 int cache_index;
297 enum pf_scroll_line_type {
298 PF_SCROLL_TRACK = 0,
299 PF_SCROLL_ALBUM,
300 PF_MAX_SCROLL_LINES
303 struct pf_scroll_line_info {
304 long ticks; /* number of ticks between each move */
305 long delay; /* number of ticks to delay starting scrolling */
306 int step; /* pixels to move */
307 long next_scroll; /* tick of the next move */
310 struct pf_scroll_line {
311 int width; /* width of the string */
312 int offset; /* x coordinate of the string */
313 int step; /* 0 if scroll is disabled. otherwise, pixels to move */
314 long start_tick; /* tick when to start scrolling */
317 struct pfraw_header {
318 int32_t width; /* bmap width in pixels */
319 int32_t height; /* bmap height in pixels */
322 enum show_album_name_values {
323 ALBUM_NAME_HIDE = 0,
324 ALBUM_NAME_BOTTOM,
325 ALBUM_NAME_TOP
327 static char* show_album_name_conf[] =
329 "hide",
330 "bottom",
331 "top"
334 #define MAX_SPACING 40
335 #define MAX_MARGIN 80
337 /* config values and their defaults */
338 static int slide_spacing = DISPLAY_WIDTH / 4;
339 static int center_margin = (LCD_WIDTH - DISPLAY_WIDTH) / 12;
340 static int num_slides = 4;
341 static int zoom = 100;
342 static bool show_fps = false;
343 static int auto_wps = 0;
344 static int last_album = 0;
345 static int backlight_mode = 0;
346 static bool resize = true;
347 static int cache_version = 0;
348 static int show_album_name = (LCD_HEIGHT > 100)
349 ? ALBUM_NAME_TOP : ALBUM_NAME_BOTTOM;
351 static struct configdata config[] =
353 { TYPE_INT, 0, MAX_SPACING, { .int_p = &slide_spacing }, "slide spacing",
354 NULL },
355 { TYPE_INT, 0, MAX_MARGIN, { .int_p = &center_margin }, "center margin",
356 NULL },
357 { TYPE_INT, 0, MAX_SLIDES_COUNT, { .int_p = &num_slides }, "slides count",
358 NULL },
359 { TYPE_INT, 0, 300, { .int_p = &zoom }, "zoom", NULL },
360 { TYPE_BOOL, 0, 1, { .bool_p = &show_fps }, "show fps", NULL },
361 { TYPE_BOOL, 0, 1, { .bool_p = &resize }, "resize", NULL },
362 { TYPE_INT, 0, 100, { .int_p = &cache_version }, "cache version", NULL },
363 { TYPE_ENUM, 0, 3, { .int_p = &show_album_name }, "show album name",
364 show_album_name_conf },
365 { TYPE_INT, 0, 2, { .int_p = &auto_wps }, "auto wps", NULL },
366 { TYPE_INT, 0, 999999, { .int_p = &last_album }, "last album", NULL },
367 { TYPE_INT, 0, 1, { .int_p = &backlight_mode }, "backlight", NULL }
370 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
372 /** below we allocate the memory we want to use **/
374 static pix_t *buffer; /* for now it always points to the lcd framebuffer */
375 static uint8_t reflect_table[REFLECT_HEIGHT];
376 static struct slide_data center_slide;
377 static struct slide_data left_slides[MAX_SLIDES_COUNT];
378 static struct slide_data right_slides[MAX_SLIDES_COUNT];
379 static int slide_frame;
380 static int step;
381 static int target;
382 static int fade;
383 static int center_index = 0; /* index of the slide that is in the center */
384 static int itilt;
385 static PFreal offsetX;
386 static PFreal offsetY;
387 static int number_of_slides;
389 static struct slide_cache cache[SLIDE_CACHE_SIZE];
390 static int cache_free;
391 static int cache_used = -1;
392 static int cache_left_index = -1;
393 static int cache_right_index = -1;
394 static int cache_center_index = -1;
396 /* use long for aligning */
397 unsigned long thread_stack[THREAD_STACK_SIZE / sizeof(long)];
398 /* queue (as array) for scheduling load_surface */
400 static int empty_slide_hid;
402 unsigned int thread_id;
403 struct event_queue thread_q;
405 static struct tagcache_search tcs;
407 static struct buflib_context buf_ctx;
409 static struct album_data *album;
410 static char *album_names;
411 static int album_count;
413 static struct track_data *tracks;
414 static char *track_names;
415 static size_t borrowed = 0;
416 static int track_count;
417 static int track_index;
418 static int selected_track;
419 static int selected_track_pulse;
420 void reset_track_list(void);
422 void * buf;
423 size_t buf_size;
425 static bool thread_is_running;
427 static int cover_animation_keyframe;
428 static int extra_fade;
430 static struct pf_scroll_line_info scroll_line_info;
431 static struct pf_scroll_line scroll_lines[PF_MAX_SCROLL_LINES];
432 static int prev_albumtxt_index = -1;
433 static int last_selected_track = -1;
435 static int start_index_track_list = 0;
436 static int track_list_visible_entries = 0;
437 static int track_list_y;
438 static int track_list_h;
441 Proposals for transitions:
443 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
444 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
446 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
448 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
450 TODO:
451 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
452 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
454 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
456 enum pf_states {
457 pf_idle = 0,
458 pf_scrolling,
459 pf_cover_in,
460 pf_show_tracks,
461 pf_cover_out
464 static int pf_state;
466 /** code */
467 static bool free_slide_prio(int prio);
468 static inline unsigned fade_color(pix_t c, unsigned a);
469 bool load_new_slide(void);
470 int load_surface(int);
472 static inline PFreal fmul(PFreal a, PFreal b)
474 return (a*b) >> PFREAL_SHIFT;
478 * This version preshifts each operand, which is useful when we know how many
479 * of the least significant bits will be empty, or are worried about overflow
480 * in a particular calculation
482 static inline PFreal fmuln(PFreal a, PFreal b, int ps1, int ps2)
484 return ((a >> ps1) * (b >> ps2)) >> (PFREAL_SHIFT - ps1 - ps2);
487 /* ARMv5+ has a clz instruction equivalent to our function.
489 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
490 static inline int clz(uint32_t v)
492 return __builtin_clz(v);
495 /* Otherwise, use our clz, which can be inlined */
496 #elif defined(CPU_COLDFIRE)
497 /* This clz is based on the log2(n) implementation at
498 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
499 * A clz benchmark plugin showed this to be about 14% faster on coldfire
500 * than the LUT-based version.
502 static inline int clz(uint32_t v)
504 int r = 32;
505 if (v >= 0x10000)
507 v >>= 16;
508 r -= 16;
510 if (v & 0xff00)
512 v >>= 8;
513 r -= 8;
515 if (v & 0xf0)
517 v >>= 4;
518 r -= 4;
520 if (v & 0xc)
522 v >>= 2;
523 r -= 2;
525 if (v & 2)
527 v >>= 1;
528 r -= 1;
530 r -= v;
531 return r;
533 #else
534 static const char clz_lut[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
535 0, 0, 0, 0, 0, 0, 0, 0 };
536 /* This clz is based on the log2(n) implementation at
537 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
538 * It is not any faster than the one above, but trades 16B in the lookup table
539 * for a savings of 12B per each inlined call.
541 static inline int clz(uint32_t v)
543 int r = 28;
544 if (v >= 0x10000)
546 v >>= 16;
547 r -= 16;
549 if (v & 0xff00)
551 v >>= 8;
552 r -= 8;
554 if (v & 0xf0)
556 v >>= 4;
557 r -= 4;
559 return r + clz_lut[v];
561 #endif
563 /* Return the maximum possible left shift for a signed int32, without
564 * overflow
566 static inline int allowed_shift(int32_t val)
568 uint32_t uval = val ^ (val >> 31);
569 return clz(uval) - 1;
572 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
573 * num and den before dividing.
575 static inline PFreal fdiv(PFreal num, PFreal den)
577 int shift = allowed_shift(num);
578 shift = MIN(PFREAL_SHIFT, shift);
579 num <<= shift;
580 den >>= PFREAL_SHIFT - shift;
581 return num / den;
584 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
585 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
586 #define fabs(a) (a < 0 ? -a : a)
587 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
589 #if CONFIG_CPU == SH7034
590 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
591 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
592 #else
593 #define MULUQ(a, b) ((a) * (b))
594 #endif
597 #if 0
598 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
599 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
601 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
602 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
604 static inline PFreal fmul(PFreal a, PFreal b)
606 return (a*b) >> PFREAL_SHIFT;
609 static inline PFreal fdiv(PFreal n, PFreal m)
611 return (n<<(PFREAL_SHIFT))/m;
613 #endif
615 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
616 static const short sin_tab[] = {
617 0, 100, 200, 297, 392, 483, 569, 650,
618 724, 792, 851, 903, 946, 980, 1004, 1019,
619 1024, 1019, 1004, 980, 946, 903, 851, 792,
620 724, 650, 569, 483, 392, 297, 200, 100,
621 0, -100, -200, -297, -392, -483, -569, -650,
622 -724, -792, -851, -903, -946, -980, -1004, -1019,
623 -1024, -1019, -1004, -980, -946, -903, -851, -792,
624 -724, -650, -569, -483, -392, -297, -200, -100,
628 static inline PFreal fsin(int iangle)
630 iangle &= IANGLE_MASK;
632 int i = (iangle >> 4);
633 PFreal p = sin_tab[i];
634 PFreal q = sin_tab[(i+1)];
635 PFreal g = (q - p);
636 return p + g * (iangle-i*16)/16;
639 static inline PFreal fcos(int iangle)
641 return fsin(iangle + (IANGLE_MAX >> 2));
644 static inline unsigned scale_val(unsigned val, unsigned bits)
646 val = val * ((1 << bits) - 1);
647 return ((val >> 8) + val + 128) >> 8;
650 static void output_row_8_transposed(uint32_t row, void * row_in,
651 struct scaler_context *ctx)
653 pix_t *dest = (pix_t*)ctx->bm->data + row;
654 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
655 #ifdef USEGSLIB
656 uint8_t *qp = (uint8_t*)row_in;
657 for (; dest < end; dest += ctx->bm->height)
658 *dest = *qp++;
659 #else
660 struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
661 unsigned r, g, b;
662 for (; dest < end; dest += ctx->bm->height)
664 r = scale_val(qp->red, 5);
665 g = scale_val(qp->green, 6);
666 b = scale_val((qp++)->blue, 5);
667 *dest = LCD_RGBPACK_LCD(r,g,b);
669 #endif
672 static void output_row_32_transposed(uint32_t row, void * row_in,
673 struct scaler_context *ctx)
675 pix_t *dest = (pix_t*)ctx->bm->data + row;
676 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
677 #ifdef USEGSLIB
678 uint32_t *qp = (uint32_t*)row_in;
679 for (; dest < end; dest += ctx->bm->height)
680 *dest = SC_OUT(*qp++, ctx);
681 #else
682 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
683 int r, g, b;
684 for (; dest < end; dest += ctx->bm->height)
686 r = scale_val(SC_OUT(qp->r, ctx), 5);
687 g = scale_val(SC_OUT(qp->g, ctx), 6);
688 b = scale_val(SC_OUT(qp->b, ctx), 5);
689 qp++;
690 *dest = LCD_RGBPACK_LCD(r,g,b);
692 #endif
695 #ifdef HAVE_LCD_COLOR
696 static void output_row_32_transposed_fromyuv(uint32_t row, void * row_in,
697 struct scaler_context *ctx)
699 pix_t *dest = (pix_t*)ctx->bm->data + row;
700 pix_t *end = dest + ctx->bm->height * ctx->bm->width;
701 struct uint32_rgb *qp = (struct uint32_rgb*)row_in;
702 for (; dest < end; dest += ctx->bm->height)
704 unsigned r, g, b, y, u, v;
705 y = SC_OUT(qp->b, ctx);
706 u = SC_OUT(qp->g, ctx);
707 v = SC_OUT(qp->r, ctx);
708 qp++;
709 yuv_to_rgb(y, u, v, &r, &g, &b);
710 r = scale_val(r, 5);
711 g = scale_val(g, 6);
712 b = scale_val(b, 5);
713 *dest = LCD_RGBPACK_LCD(r, g, b);
716 #endif
718 static unsigned int get_size(struct bitmap *bm)
720 return bm->width * bm->height * sizeof(pix_t);
723 const struct custom_format format_transposed = {
724 .output_row_8 = output_row_8_transposed,
725 #ifdef HAVE_LCD_COLOR
726 .output_row_32 = {
727 output_row_32_transposed,
728 output_row_32_transposed_fromyuv
730 #else
731 .output_row_32 = output_row_32_transposed,
732 #endif
733 .get_size = get_size
736 static const struct button_mapping* get_context_map(int context)
738 return pf_contexts[context & ~CONTEXT_PLUGIN];
741 /* scrolling */
742 static void init_scroll_lines(void)
744 int i;
745 static const char scroll_tick_table[16] = {
746 /* Hz values:
747 1, 1.25, 1.55, 2, 2.5, 3.12, 4, 5, 6.25, 8.33, 10, 12.5, 16.7, 20, 25, 33 */
748 100, 80, 64, 50, 40, 32, 25, 20, 16, 12, 10, 8, 6, 5, 4, 3
751 scroll_line_info.ticks = scroll_tick_table[rb->global_settings->scroll_speed];
752 scroll_line_info.step = rb->global_settings->scroll_step;
753 scroll_line_info.delay = rb->global_settings->scroll_delay / (HZ / 10);
754 scroll_line_info.next_scroll = *rb->current_tick;
755 for (i = 0; i < PF_MAX_SCROLL_LINES; i++)
756 scroll_lines[i].step = 0;
759 static void set_scroll_line(const char *str, enum pf_scroll_line_type type)
761 struct pf_scroll_line *s = &scroll_lines[type];
762 s->width = mylcd_getstringsize(str, NULL, NULL);
763 s->step = 0;
764 s->offset = 0;
765 s->start_tick = *rb->current_tick + scroll_line_info.delay;
766 if (LCD_WIDTH - s->width < 0)
767 s->step = scroll_line_info.step;
768 else
769 s->offset = (LCD_WIDTH - s->width) / 2;
772 static int get_scroll_line_offset(enum pf_scroll_line_type type)
774 return scroll_lines[type].offset;
777 static void update_scroll_lines(void)
779 int i;
781 if (TIME_BEFORE(*rb->current_tick, scroll_line_info.next_scroll))
782 return;
784 scroll_line_info.next_scroll = *rb->current_tick + scroll_line_info.ticks;
786 for (i = 0; i < PF_MAX_SCROLL_LINES; i++)
788 struct pf_scroll_line *s = &scroll_lines[i];
789 if (s->step && TIME_BEFORE(s->start_tick, *rb->current_tick))
791 s->offset -= s->step;
793 if (s->offset >= 0) {
794 /* at beginning of line */
795 s->offset = 0;
796 s->step = scroll_line_info.step;
797 s->start_tick = *rb->current_tick + scroll_line_info.delay * 2;
799 if (s->offset <= LCD_WIDTH - s->width) {
800 /* at end of line */
801 s->offset = LCD_WIDTH - s->width;
802 s->step = -scroll_line_info.step;
803 s->start_tick = *rb->current_tick + scroll_line_info.delay * 2;
809 /* Create the lookup table with the scaling values for the reflections */
810 void init_reflect_table(void)
812 int i;
813 for (i = 0; i < REFLECT_HEIGHT; i++)
814 reflect_table[i] =
815 (768 * (REFLECT_HEIGHT - i) + (5 * REFLECT_HEIGHT / 2)) /
816 (5 * REFLECT_HEIGHT);
820 Create an index of all albums from the database.
821 Also store the album names so we can access them later.
823 int create_album_index(void)
825 album = ((struct album_data *)(buf_size + (char *) buf)) - 1;
826 rb->memset(&tcs, 0, sizeof(struct tagcache_search) );
827 album_count = 0;
828 rb->tagcache_search(&tcs, tag_album);
829 unsigned int l, name_idx = 0;
830 album_names = buf;
831 while (rb->tagcache_get_next(&tcs))
833 buf_size -= sizeof(struct album_data);
834 l = tcs.result_len;
835 album[-album_count].name_idx = name_idx;
837 if ( l > buf_size )
838 /* not enough memory */
839 return ERROR_BUFFER_FULL;
841 rb->strcpy(buf, tcs.result);
842 buf_size -= l;
843 buf = l + (char *)buf;
844 album[-album_count].seek = tcs.result_seek;
845 name_idx += l;
846 album_count++;
848 rb->tagcache_search_finish(&tcs);
849 ALIGN_BUFFER(buf, buf_size, 4);
850 int i;
851 struct album_data* tmp_album = (struct album_data*)buf;
852 for (i = album_count - 1; i >= 0; i--)
853 tmp_album[i] = album[-i];
854 album = tmp_album;
855 buf = album + album_count;
856 return (album_count > 0) ? 0 : ERROR_NO_ALBUMS;
860 Return a pointer to the album name of the given slide_index
862 char* get_album_name(const int slide_index)
864 return album_names + album[slide_index].name_idx;
868 Return a pointer to the track name of the active album
869 create_track_index has to be called first.
871 char* get_track_name(const int track_index)
873 if ( track_index < track_count )
874 return track_names + tracks[track_index].name_idx;
875 return 0;
877 #if PF_PLAYBACK_CAPABLE
878 char* get_track_filename(const int track_index)
880 if ( track_index < track_count )
881 return track_names + tracks[track_index].filename_idx;
882 return 0;
884 #endif
886 int get_wps_current_index(void)
888 struct mp3entry *id3 = rb->audio_current_track();
889 if(id3 && id3->album) {
890 int i;
891 for( i=0; i < album_count; i++ )
893 if(!rb->strcmp(album_names + album[i].name_idx, id3->album))
894 return i;
897 return last_album;
901 Compare two unsigned ints passed via pointers.
903 int compare_tracks (const void *a_v, const void *b_v)
905 uint32_t a = ((struct track_data *)a_v)->sort;
906 uint32_t b = ((struct track_data *)b_v)->sort;
907 return (int)(a - b);
911 Create the track index of the given slide_index.
913 void create_track_index(const int slide_index)
915 if ( slide_index == track_index )
916 return;
917 track_index = slide_index;
919 if (!rb->tagcache_search(&tcs, tag_title))
920 goto fail;
922 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
923 track_count=0;
924 int string_index = 0, track_num;
925 int disc_num;
926 size_t out = 0;
927 track_names = (char *)buflib_buffer_out(&buf_ctx, &out);
928 borrowed += out;
929 int avail = borrowed;
930 tracks = (struct track_data*)(track_names + borrowed);
931 while (rb->tagcache_get_next(&tcs))
933 int len = 0, fn_idx = 0;
935 avail -= sizeof(struct track_data);
936 track_num = rb->tagcache_get_numeric(&tcs, tag_tracknumber);
937 disc_num = rb->tagcache_get_numeric(&tcs, tag_discnumber);
939 if (disc_num < 0)
940 disc_num = 0;
941 retry:
942 if (track_num > 0)
944 if (disc_num)
945 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
946 "%d.%02d: %s", disc_num, track_num, tcs.result);
947 else
948 fn_idx = 1 + rb->snprintf(track_names + string_index , avail,
949 "%d: %s", track_num, tcs.result);
951 else
953 track_num = 0;
954 fn_idx = 1 + rb->snprintf(track_names + string_index, avail,
955 "%s", tcs.result);
957 if (fn_idx <= 0)
958 goto fail;
959 #if PF_PLAYBACK_CAPABLE
960 int remain = avail - fn_idx;
961 if (remain >= MAX_PATH)
962 { /* retrieve filename for building the playlist */
963 rb->tagcache_retrieve(&tcs, tcs.idx_id, tag_filename,
964 track_names + string_index + fn_idx, remain);
965 len = fn_idx + rb->strlen(track_names + string_index + fn_idx) + 1;
966 /* make sure track name and file name are really split by a \0, else
967 * get_track_name might fail */
968 *(track_names + string_index + fn_idx -1) = '\0';
971 else /* request more buffer so that track and filename fit */
972 len = (avail - remain) + MAX_PATH;
973 #else
974 len = fn_idx;
975 #endif
976 if (len > avail)
978 while (len > avail)
980 if (!free_slide_prio(0))
981 goto fail;
982 out = 0;
983 buflib_buffer_out(&buf_ctx, &out);
984 avail += out;
985 borrowed += out;
987 struct track_data *new_tracks = (struct track_data *)(out + (uintptr_t)tracks);
988 unsigned int bytes = track_count * sizeof(struct track_data);
989 if (track_count)
990 rb->memmove(new_tracks, tracks, bytes);
991 tracks = new_tracks;
993 goto retry;
996 avail -= len;
997 tracks--;
998 tracks->sort = (disc_num << 24) + (track_num << 14) + track_count;
999 tracks->name_idx = string_index;
1000 tracks->seek = tcs.result_seek;
1001 #if PF_PLAYBACK_CAPABLE
1002 tracks->filename_idx = fn_idx + string_index;
1003 #endif
1004 track_count++;
1005 string_index += len;
1008 rb->tagcache_search_finish(&tcs);
1010 /* now fix the track list order */
1011 rb->qsort(tracks, track_count, sizeof(struct track_data), compare_tracks);
1012 return;
1013 fail:
1014 track_count = 0;
1015 return;
1019 Determine filename of the album art for the given slide_index and
1020 store the result in buf.
1021 The algorithm looks for the first track of the given album uses
1022 find_albumart to find the filename.
1024 bool get_albumart_for_index_from_db(const int slide_index, char *buf,
1025 int buflen)
1027 if ( slide_index == -1 )
1029 rb->strlcpy( buf, EMPTY_SLIDE, buflen );
1032 if (!rb->tagcache_search(&tcs, tag_filename))
1033 return false;
1035 bool result;
1036 /* find the first track of the album */
1037 rb->tagcache_search_add_filter(&tcs, tag_album, album[slide_index].seek);
1039 if ( rb->tagcache_get_next(&tcs) ) {
1040 struct mp3entry id3;
1041 int fd;
1043 #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
1044 if (rb->tagcache_fill_tags(&id3, tcs.result))
1046 rb->strlcpy(id3.path, tcs.result, sizeof(id3.path));
1048 else
1049 #endif
1051 fd = rb->open(tcs.result, O_RDONLY);
1052 rb->get_metadata(&id3, fd, tcs.result);
1053 rb->close(fd);
1055 if ( search_albumart_files(&id3, ":", buf, buflen) )
1056 result = true;
1057 else
1058 result = false;
1060 else {
1061 /* did not find a matching track */
1062 result = false;
1064 rb->tagcache_search_finish(&tcs);
1065 return result;
1069 Draw the PictureFlow logo
1071 void draw_splashscreen(void)
1073 unsigned char * buf_tmp = buf;
1074 size_t buf_tmp_size = buf_size;
1075 struct screen* display = rb->screens[SCREEN_MAIN];
1076 #if FB_DATA_SZ > 1
1077 ALIGN_BUFFER(buf_tmp, buf_tmp_size, sizeof(fb_data));
1078 #endif
1079 struct bitmap logo = {
1080 #if LCD_WIDTH < 200
1081 .width = 100,
1082 .height = 18,
1083 #else
1084 .width = 193,
1085 .height = 34,
1086 #endif
1087 .data = buf_tmp
1089 int ret = rb->read_bmp_file(SPLASH_BMP, &logo, buf_tmp_size,
1090 FORMAT_NATIVE, NULL);
1091 #if LCD_DEPTH > 1
1092 rb->lcd_set_background(N_BRIGHT(0));
1093 rb->lcd_set_foreground(N_BRIGHT(255));
1094 #else
1095 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
1096 #endif
1097 rb->lcd_clear_display();
1099 if (ret > 0)
1101 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
1102 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE ^ DRMODE_INVERSEVID);
1103 #endif
1104 display->bitmap(logo.data, (LCD_WIDTH - logo.width) / 2, 10,
1105 logo.width, logo.height);
1106 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
1107 rb->lcd_set_drawmode(PICTUREFLOW_DRMODE);
1108 #endif
1111 rb->lcd_update();
1116 Draw a simple progress bar
1118 void draw_progressbar(int step)
1120 int txt_w, txt_h;
1121 const int bar_height = 22;
1122 const int w = LCD_WIDTH - 20;
1123 const int x = 10;
1125 rb->lcd_getstringsize("Preparing album artwork", &txt_w, &txt_h);
1127 int y = (LCD_HEIGHT - txt_h)/2;
1129 rb->lcd_putsxy((LCD_WIDTH - txt_w)/2, y, "Preparing album artwork");
1130 y += (txt_h + 5);
1132 #if LCD_DEPTH > 1
1133 rb->lcd_set_foreground(N_BRIGHT(100));
1134 #endif
1135 rb->lcd_drawrect(x, y, w+2, bar_height);
1136 #if LCD_DEPTH > 1
1137 rb->lcd_set_foreground(N_PIX(165, 231, 82));
1138 #endif
1140 rb->lcd_fillrect(x+1, y+1, step * w / album_count, bar_height-2);
1141 #if LCD_DEPTH > 1
1142 rb->lcd_set_foreground(N_BRIGHT(255));
1143 #endif
1144 rb->lcd_update();
1145 rb->yield();
1148 /* Calculate modified FNV hash of string
1149 * has good avalanche behaviour and uniform distribution
1150 * see http://home.comcast.net/~bretm/hash/ */
1151 unsigned int mfnv(char *str)
1153 const unsigned int p = 16777619;
1154 unsigned int hash = 0x811C9DC5; // 2166136261;
1156 while(*str)
1157 hash = (hash ^ *str++) * p;
1158 hash += hash << 13;
1159 hash ^= hash >> 7;
1160 hash += hash << 3;
1161 hash ^= hash >> 17;
1162 hash += hash << 5;
1163 return hash;
1167 Save the given bitmap as filename in the pfraw format
1169 bool save_pfraw(char* filename, struct bitmap *bm)
1171 struct pfraw_header bmph;
1172 bmph.width = bm->width;
1173 bmph.height = bm->height;
1174 int fh = rb->creat( filename , 0666);
1175 if( fh < 0 ) return false;
1176 rb->write( fh, &bmph, sizeof( struct pfraw_header ) );
1177 pix_t *data = (pix_t*)( bm->data );
1178 int y;
1179 for( y = 0; y < bm->height; y++ )
1181 rb->write( fh, data , sizeof( pix_t ) * bm->width );
1182 data += bm->width;
1184 rb->close( fh );
1185 return true;
1189 Precomupte the album art images and store them in CACHE_PREFIX.
1190 Use the "?" bitmap if image is not found.
1192 bool create_albumart_cache(void)
1194 int ret;
1196 int i, slides = 0;
1197 struct bitmap input_bmp;
1199 char pfraw_file[MAX_PATH];
1200 char albumart_file[MAX_PATH];
1201 unsigned int format = FORMAT_NATIVE;
1202 bool update = (cache_version == CACHE_UPDATE);
1203 if (resize)
1204 format |= FORMAT_RESIZE|FORMAT_KEEP_ASPECT;
1205 for (i=0; i < album_count; i++)
1207 draw_progressbar(i);
1209 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%x.pfraw",
1210 mfnv(get_album_name(i)));
1211 /* delete existing cache, so it's a true rebuild */
1212 if(rb->file_exists(pfraw_file)) {
1213 if(update) {
1214 slides++;
1215 continue;
1217 rb->remove(pfraw_file);
1219 if (!get_albumart_for_index_from_db(i, albumart_file, MAX_PATH))
1220 rb->strcpy(albumart_file, EMPTY_SLIDE_BMP);
1222 input_bmp.data = buf;
1223 input_bmp.width = DISPLAY_WIDTH;
1224 input_bmp.height = DISPLAY_HEIGHT;
1225 ret = read_image_file(albumart_file, &input_bmp, buf_size,
1226 format, &format_transposed);
1227 if (ret <= 0) {
1228 rb->splashf(HZ, "Album art is bad: %s", get_album_name(i));
1229 rb->strcpy(albumart_file, EMPTY_SLIDE_BMP);
1230 ret = read_image_file(albumart_file, &input_bmp, buf_size,
1231 format, &format_transposed);
1232 if(ret <= 0)
1233 continue;
1235 if (!save_pfraw(pfraw_file, &input_bmp))
1237 rb->splash(HZ, "Could not write bmp");
1238 continue;
1240 slides++;
1241 if ( rb->button_get(false) == PF_MENU ) return false;
1243 draw_progressbar(i);
1244 if ( slides == 0 ) {
1245 /* Warn the user that we couldn't find any albumart */
1246 rb->splash(2*HZ, "No album art found");
1247 return false;
1249 return true;
1253 Create the "?" slide, that is shown while loading
1254 or when no cover was found.
1256 int create_empty_slide(bool force)
1258 if ( force || ! rb->file_exists( EMPTY_SLIDE ) ) {
1259 struct bitmap input_bmp;
1260 input_bmp.width = DISPLAY_WIDTH;
1261 input_bmp.height = DISPLAY_HEIGHT;
1262 #if LCD_DEPTH > 1
1263 input_bmp.format = FORMAT_NATIVE;
1264 #endif
1265 input_bmp.data = (char*)buf;
1266 scaled_read_bmp_file(EMPTY_SLIDE_BMP, &input_bmp,
1267 buf_size,
1268 FORMAT_NATIVE|FORMAT_RESIZE|FORMAT_KEEP_ASPECT,
1269 &format_transposed);
1270 if (!save_pfraw(EMPTY_SLIDE, &input_bmp))
1271 return false;
1274 return true;
1278 Thread used for loading and preparing bitmaps in the background
1280 void thread(void)
1282 long sleep_time = 5 * HZ;
1283 struct queue_event ev;
1284 while (1) {
1285 rb->queue_wait_w_tmo(&thread_q, &ev, sleep_time);
1286 switch (ev.id) {
1287 case EV_EXIT:
1288 return;
1289 case EV_WAKEUP:
1290 /* we just woke up */
1291 break;
1293 if(ev.id != SYS_TIMEOUT)
1294 while ( load_new_slide() ) {
1295 rb->yield();
1296 switch (ev.id) {
1297 case EV_EXIT:
1298 return;
1306 End the thread by posting the EV_EXIT event
1308 void end_pf_thread(void)
1310 if ( thread_is_running ) {
1311 rb->queue_post(&thread_q, EV_EXIT, 0);
1312 rb->thread_wait(thread_id);
1313 /* remove the thread's queue from the broadcast list */
1314 rb->queue_delete(&thread_q);
1315 thread_is_running = false;
1321 Create the thread an setup the event queue
1323 bool create_pf_thread(void)
1325 /* put the thread's queue in the bcast list */
1326 rb->queue_init(&thread_q, true);
1327 if ((thread_id = rb->create_thread(
1328 thread,
1329 thread_stack,
1330 sizeof(thread_stack),
1332 "Picture load thread"
1333 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE / 2,
1334 PRIORITY_REALTIME + 1))
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 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 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1527 if (!hid) {
1528 rb->close( fh );
1529 return 0;
1532 rb->yield(); /* allow audio to play when fast scrolling */
1533 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1535 bm->width = bmph.width;
1536 bm->height = bmph.height;
1537 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1539 int y;
1540 for( y = 0; y < bm->height; y++ )
1542 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1543 data += bm->width;
1545 rb->close( fh );
1546 return hid;
1551 Load the surface for the given slide_index into the cache at cache_index.
1553 static inline bool load_and_prepare_surface(const int slide_index,
1554 const int cache_index,
1555 const int prio)
1557 char pfraw_file[MAX_PATH];
1558 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%x.pfraw",
1559 mfnv(get_album_name(slide_index)));
1561 int hid = read_pfraw(pfraw_file, prio);
1562 if (!hid)
1563 return false;
1565 cache[cache_index].hid = hid;
1567 if ( cache_index < SLIDE_CACHE_SIZE ) {
1568 cache[cache_index].index = slide_index;
1571 return true;
1576 Load the "next" slide that we can load, freeing old slides if needed, provided
1577 that they are further from center_index than the current slide
1579 bool load_new_slide(void)
1581 int i = -1;
1582 if (cache_center_index != -1)
1584 int next, prev;
1585 if (cache[cache_center_index].index != center_index)
1587 if (cache[cache_center_index].index < center_index)
1589 cache_center_index = seek_right_while(cache_center_index,
1590 cache[next_].index <= center_index);
1591 prev = cache_center_index;
1592 next = cache[cache_center_index].next;
1594 else
1596 cache_center_index = seek_left_while(cache_center_index,
1597 cache[next_].index >= center_index);
1598 next = cache_center_index;
1599 prev = cache[cache_center_index].prev;
1601 if (cache[cache_center_index].index != center_index)
1603 if (cache_free == -1)
1604 free_slide_prio(0);
1605 i = lla_pop_head(&cache_free);
1606 if (!load_and_prepare_surface(center_index, i, 0))
1607 goto fail_and_refree;
1608 if (cache[next].index == -1)
1610 if (cache[prev].index == -1)
1611 goto insert_first_slide;
1612 else
1613 next = cache[prev].next;
1615 lla_insert(i, next);
1616 if (cache[i].index < cache[cache_used].index)
1617 cache_used = i;
1618 cache_center_index = i;
1619 cache_left_index = i;
1620 cache_right_index = i;
1621 return true;
1624 if (cache[cache_left_index].index >
1625 cache[cache_center_index].index)
1626 cache_left_index = cache_center_index;
1627 if (cache[cache_right_index].index <
1628 cache[cache_center_index].index)
1629 cache_right_index = cache_center_index;
1630 cache_left_index = seek_left_while(cache_left_index,
1631 cache[ind_].index - 1 == cache[next_].index);
1632 cache_right_index = seek_right_while(cache_right_index,
1633 cache[ind_].index - 1 == cache[next_].index);
1634 int prio_l = cache[cache_center_index].index -
1635 cache[cache_left_index].index + 1;
1636 int prio_r = cache[cache_right_index].index -
1637 cache[cache_center_index].index + 1;
1638 if ((prio_l < prio_r ||
1639 cache[cache_right_index].index >= number_of_slides) &&
1640 cache[cache_left_index].index > 0)
1642 if (cache_free == -1 && !free_slide_prio(prio_l))
1643 return false;
1644 i = lla_pop_head(&cache_free);
1645 if (load_and_prepare_surface(cache[cache_left_index].index
1646 - 1, i, prio_l))
1648 lla_insert_before(&cache_used, i, cache_left_index);
1649 cache_left_index = i;
1650 return true;
1652 } else if(cache[cache_right_index].index < number_of_slides - 1)
1654 if (cache_free == -1 && !free_slide_prio(prio_r))
1655 return false;
1656 i = lla_pop_head(&cache_free);
1657 if (load_and_prepare_surface(cache[cache_right_index].index
1658 + 1, i, prio_r))
1660 lla_insert_after(i, cache_right_index);
1661 cache_right_index = i;
1662 return true;
1665 } else {
1666 i = lla_pop_head(&cache_free);
1667 if (load_and_prepare_surface(center_index, i, 0))
1669 insert_first_slide:
1670 cache[i].next = i;
1671 cache[i].prev = i;
1672 cache_center_index = i;
1673 cache_left_index = i;
1674 cache_right_index = i;
1675 cache_used = i;
1676 return true;
1679 fail_and_refree:
1680 if (i != -1)
1682 lla_insert_tail(&cache_free, i);
1684 return false;
1689 Get a slide from the buffer
1691 static inline struct dim *get_slide(const int hid)
1693 if (!hid)
1694 return NULL;
1696 struct dim *bmp;
1698 bmp = buflib_get_data(&buf_ctx, hid);
1700 return bmp;
1705 Return the requested surface
1707 static inline struct dim *surface(const int slide_index)
1709 if (slide_index < 0)
1710 return 0;
1711 if (slide_index >= number_of_slides)
1712 return 0;
1713 int i;
1714 if ((i = cache_used ) != -1)
1716 do {
1717 if (cache[i].index == slide_index)
1718 return get_slide(cache[i].hid);
1719 i = cache[i].next;
1720 } while (i != cache_used);
1722 return get_slide(empty_slide_hid);
1726 adjust slides so that they are in "steady state" position
1728 void reset_slides(void)
1730 center_slide.angle = 0;
1731 center_slide.cx = 0;
1732 center_slide.cy = 0;
1733 center_slide.distance = 0;
1734 center_slide.slide_index = center_index;
1736 int i;
1737 for (i = 0; i < num_slides; i++) {
1738 struct slide_data *si = &left_slides[i];
1739 si->angle = itilt;
1740 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1741 si->cy = offsetY;
1742 si->slide_index = center_index - 1 - i;
1743 si->distance = 0;
1746 for (i = 0; i < num_slides; i++) {
1747 struct slide_data *si = &right_slides[i];
1748 si->angle = -itilt;
1749 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1750 si->cy = offsetY;
1751 si->slide_index = center_index + 1 + i;
1752 si->distance = 0;
1758 Updates look-up table and other stuff necessary for the rendering.
1759 Call this when the viewport size or slide dimension is changed.
1761 * To calculate the offset that will provide the proper margin, we use the same
1762 * projection used to render the slides. The solution for xc, the slide center,
1763 * is:
1764 * xp * (zo + xs * sin(r))
1765 * xc = xp - xs * cos(r) + ───────────────────────
1767 * TODO: support moving the side slides toward or away from the camera
1769 void recalc_offsets(void)
1771 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1772 PFreal zo;
1773 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1774 PFREAL_ONE) * zoom / 100;
1775 PFreal cosr, sinr;
1777 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1778 cosr = fcos(-itilt);
1779 sinr = fsin(-itilt);
1780 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1781 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1782 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1783 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1784 / CAM_DIST;
1785 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1790 Fade the given color by spreading the fb_data (ushort)
1791 to an uint, multiply and compress the result back to a ushort.
1793 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1794 static inline unsigned fade_color(pix_t c, unsigned a)
1796 unsigned int result;
1797 c = swap16(c);
1798 a = (a + 2) & 0x1fc;
1799 result = ((c & 0xf81f) * a) & 0xf81f00;
1800 result |= ((c & 0x7e0) * a) & 0x7e000;
1801 result >>= 8;
1802 return swap16(result);
1804 #elif LCD_PIXELFORMAT == RGB565
1805 static inline unsigned fade_color(pix_t c, unsigned a)
1807 unsigned int result;
1808 a = (a + 2) & 0x1fc;
1809 result = ((c & 0xf81f) * a) & 0xf81f00;
1810 result |= ((c & 0x7e0) * a) & 0x7e000;
1811 result >>= 8;
1812 return result;
1814 #else
1815 static inline unsigned fade_color(pix_t c, unsigned a)
1817 unsigned val = c;
1818 return MULUQ(val, a) >> 8;
1820 #endif
1823 * Render a single slide
1824 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1825 * on the slide from its center, zo is the slide's depth offset from the plane
1826 * of the display, r is the angle at which the slide is tilted, and xp is the
1827 * point on the display corresponding to xs on the slide, the projection
1828 * formulas are:
1830 * z * (xc + xs * cos(r))
1831 * xp = ──────────────────────
1832 * z + zo + xs * sin(r)
1834 * z * (xc - xp) - xp * zo
1835 * xs = ────────────────────────
1836 * xp * sin(r) - z * cos(r)
1838 * We use the xp projection once, to find the left edge of the slide on the
1839 * display. From there, we use the xs reverse projection to find the horizontal
1840 * offset from the slide center of each column on the screen, until we reach
1841 * the right edge of the slide, or the screen. The reverse projection can be
1842 * optimized by saving the numerator and denominator of the fraction, which can
1843 * then be incremented by (z + zo) and sin(r) respectively.
1845 void render_slide(struct slide_data *slide, const int alpha)
1847 struct dim *bmp = surface(slide->slide_index);
1848 if (!bmp) {
1849 return;
1851 if (slide->angle > 255 || slide->angle < -255)
1852 return;
1853 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1855 const int sw = bmp->width;
1856 const int sh = bmp->height;
1857 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1858 const int w = LCD_WIDTH;
1860 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1862 if (alpha == 256) { /* opaque -> copy table */
1863 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1864 } else { /* precalculate faded table */
1865 int i, lalpha;
1866 for (i = 0; i < REFLECT_HEIGHT; i++) {
1867 lalpha = reflect_table[i];
1868 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1872 PFreal cosr = fcos(slide->angle);
1873 PFreal sinr = fsin(slide->angle);
1874 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1875 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1876 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1877 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1878 (CAM_DIST_R + zo + fmul(xs,sinr)));
1880 /* Since we're finding the screen position of the left edge of the slide,
1881 * we round up.
1883 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1884 >> PFREAL_SHIFT;
1885 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1886 if (xi >= w) {
1887 return;
1889 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1890 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1891 xs = fdiv(xsnum, xsden);
1893 xsnumi = -CAM_DIST_R - zo;
1894 xsdeni = sinr;
1895 int x;
1896 int dy = PFREAL_ONE;
1897 for (x = xi; x < w; x++) {
1898 int column = (xs - slide_left) / PFREAL_ONE;
1899 if (column >= sw)
1900 break;
1901 if (zo || slide->angle)
1902 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1904 const pix_t *ptr = &src[column * bmp->height];
1906 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
1907 #define PIXELSTEP_Y 1
1908 #define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)])
1909 #else
1910 #define PIXELSTEP_Y BUFFER_WIDTH
1911 #define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
1912 #endif
1914 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1915 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1916 pix_t *pixel = LCDADDR(x, (LCD_HEIGHT/2)-1 );
1918 if (alpha == 256) {
1919 while (p >= plim) {
1920 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1921 p -= dy;
1922 pixel -= PIXELSTEP_Y;
1924 } else {
1925 while (p >= plim) {
1926 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1927 p -= dy;
1928 pixel -= PIXELSTEP_Y;
1931 rb->yield(); /* allow audio to play when fast scrolling */
1932 bmp = surface(slide->slide_index); /* resync surface due to yield */
1933 src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1934 ptr = &src[column * bmp->height];
1935 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1936 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1937 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1938 p + (LCD_HEIGHT/2) * dy);
1939 pixel = LCDADDR(x, (LCD_HEIGHT/2) );
1941 if (alpha == 256) {
1942 while (p < plim) {
1943 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1944 p += dy;
1945 pixel += PIXELSTEP_Y;
1947 } else {
1948 while (p < plim) {
1949 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1950 p += dy;
1951 pixel += PIXELSTEP_Y;
1954 while (p < plim2) {
1955 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1956 int lalpha = reftab[ty];
1957 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1958 p += dy;
1959 pixel += PIXELSTEP_Y;
1962 if (zo || slide->angle)
1964 xsnum += xsnumi;
1965 xsden += xsdeni;
1966 xs = fdiv(xsnum, xsden);
1967 } else
1968 xs += PFREAL_ONE;
1971 /* let the music play... */
1972 rb->yield();
1973 return;
1977 Jump the the given slide_index
1979 static inline void set_current_slide(const int slide_index)
1981 int old_center_index = center_index;
1982 step = 0;
1983 center_index = fbound(slide_index, 0, number_of_slides - 1);
1984 if (old_center_index != center_index)
1985 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1986 target = center_index;
1987 slide_frame = slide_index << 16;
1988 reset_slides();
1992 Start the animation for changing slides
1994 void start_animation(void)
1996 step = (target < center_slide.slide_index) ? -1 : 1;
1997 pf_state = pf_scrolling;
2001 Go to the previous slide
2003 void show_previous_slide(void)
2005 if (step == 0) {
2006 if (center_index > 0) {
2007 target = center_index - 1;
2008 start_animation();
2010 } else if ( step > 0 ) {
2011 target = center_index;
2012 step = (target <= center_slide.slide_index) ? -1 : 1;
2013 } else {
2014 target = fmax(0, center_index - 2);
2020 Go to the next slide
2022 void show_next_slide(void)
2024 if (step == 0) {
2025 if (center_index < number_of_slides - 1) {
2026 target = center_index + 1;
2027 start_animation();
2029 } else if ( step < 0 ) {
2030 target = center_index;
2031 step = (target < center_slide.slide_index) ? -1 : 1;
2032 } else {
2033 target = fmin(center_index + 2, number_of_slides - 1);
2039 Render the slides. Updates only the offscreen buffer.
2041 void render_all_slides(void)
2043 mylcd_set_background(G_BRIGHT(0));
2044 /* TODO: Optimizes this by e.g. invalidating rects */
2045 mylcd_clear_display();
2047 int nleft = num_slides;
2048 int nright = num_slides;
2050 int alpha;
2051 int index;
2052 if (step == 0) {
2053 /* no animation, boring plain rendering */
2054 for (index = nleft - 2; index >= 0; index--) {
2055 alpha = (index < nleft - 2) ? 256 : 128;
2056 alpha -= extra_fade;
2057 if (alpha > 0 )
2058 render_slide(&left_slides[index], alpha);
2060 for (index = nright - 2; index >= 0; index--) {
2061 alpha = (index < nright - 2) ? 256 : 128;
2062 alpha -= extra_fade;
2063 if (alpha > 0 )
2064 render_slide(&right_slides[index], alpha);
2066 } else {
2067 /* the first and last slide must fade in/fade out */
2069 /* if step<0 and nleft==1, left_slides[0] is fading in */
2070 alpha = ((step > 0) ? 0 : ((nleft == 1) ? 256 : 128)) - fade / 2;
2071 for (index = nleft - 1; index >= 0; index--) {
2072 if (alpha > 0)
2073 render_slide(&left_slides[index], alpha);
2074 alpha += 128;
2075 if (alpha > 256) alpha = 256;
2077 /* if step>0 and nright==1, right_slides[0] is fading in */
2078 alpha = ((step > 0) ? ((nright == 1) ? 128 : 0) : -128) + fade / 2;
2079 for (index = nright - 1; index >= 0; index--) {
2080 if (alpha > 0)
2081 render_slide(&right_slides[index], alpha);
2082 alpha += 128;
2083 if (alpha > 256) alpha = 256;
2086 alpha = 256;
2087 if (step != 0 && num_slides <= 2) /* fading out center slide */
2088 alpha = (step > 0) ? 256 - fade / 2 : 128 + fade / 2;
2089 render_slide(&center_slide, alpha);
2094 Updates the animation effect. Call this periodically from a timer.
2096 void update_scroll_animation(void)
2098 if (step == 0)
2099 return;
2101 int speed = 16384;
2102 int i;
2104 /* deaccelerate when approaching the target */
2105 if (true) {
2106 const int max = 2 * 65536;
2108 int fi = slide_frame;
2109 fi -= (target << 16);
2110 if (fi < 0)
2111 fi = -fi;
2112 fi = fmin(fi, max);
2114 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
2115 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
2118 slide_frame += speed * step;
2120 int index = slide_frame >> 16;
2121 int pos = slide_frame & 0xffff;
2122 int neg = 65536 - pos;
2123 int tick = (step < 0) ? neg : pos;
2124 PFreal ftick = (tick * PFREAL_ONE) >> 16;
2126 /* the leftmost and rightmost slide must fade away */
2127 fade = pos / 256;
2129 if (step < 0)
2130 index++;
2131 if (center_index != index) {
2132 center_index = index;
2133 rb->queue_post(&thread_q, EV_WAKEUP, 0);
2134 slide_frame = index << 16;
2135 center_slide.slide_index = center_index;
2136 for (i = 0; i < num_slides; i++)
2137 left_slides[i].slide_index = center_index - 1 - i;
2138 for (i = 0; i < num_slides; i++)
2139 right_slides[i].slide_index = center_index + 1 + i;
2142 center_slide.angle = (step * tick * itilt) >> 16;
2143 center_slide.cx = -step * fmul(offsetX, ftick);
2144 center_slide.cy = fmul(offsetY, ftick);
2146 if (center_index == target) {
2147 reset_slides();
2148 pf_state = pf_idle;
2149 slide_frame = center_index << 16;
2150 step = 0;
2151 fade = 256;
2152 return;
2155 for (i = 0; i < num_slides; i++) {
2156 struct slide_data *si = &left_slides[i];
2157 si->angle = itilt;
2158 si->cx =
2159 -(offsetX + slide_spacing * i * PFREAL_ONE + step
2160 * slide_spacing * ftick);
2161 si->cy = offsetY;
2164 for (i = 0; i < num_slides; i++) {
2165 struct slide_data *si = &right_slides[i];
2166 si->angle = -itilt;
2167 si->cx =
2168 offsetX + slide_spacing * i * PFREAL_ONE - step
2169 * slide_spacing * ftick;
2170 si->cy = offsetY;
2173 if (step > 0) {
2174 PFreal ftick = (neg * PFREAL_ONE) >> 16;
2175 right_slides[0].angle = -(neg * itilt) >> 16;
2176 right_slides[0].cx = fmul(offsetX, ftick);
2177 right_slides[0].cy = fmul(offsetY, ftick);
2178 } else {
2179 PFreal ftick = (pos * PFREAL_ONE) >> 16;
2180 left_slides[0].angle = (pos * itilt) >> 16;
2181 left_slides[0].cx = -fmul(offsetX, ftick);
2182 left_slides[0].cy = fmul(offsetY, ftick);
2185 /* must change direction ? */
2186 if (target < index)
2187 if (step > 0)
2188 step = -1;
2189 if (target > index)
2190 if (step < 0)
2191 step = 1;
2196 Cleanup the plugin
2198 void cleanup(void)
2200 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2201 rb->cpu_boost(false);
2202 #endif
2203 end_pf_thread();
2204 /* Turn on backlight timeout (revert to settings) */
2205 backlight_use_settings();
2207 #ifdef USEGSLIB
2208 grey_release();
2209 #endif
2213 Shows the settings menu
2215 int settings_menu(void)
2217 int selection = 0;
2218 bool old_val;
2220 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2221 "Spacing", "Centre margin", "Number of slides", "Zoom",
2222 "Show album title", "Resize Covers", "Rebuild cache",
2223 "WPS Integration", "Backlight");
2225 static const struct opt_items album_name_options[] = {
2226 { "Hide album title", -1 },
2227 { "Show at the bottom", -1 },
2228 { "Show at the top", -1 }
2230 static const struct opt_items wps_options[] = {
2231 { "Off", -1 },
2232 { "Direct", -1 },
2233 { "Via Track list", -1 }
2235 static const struct opt_items backlight_options[] = {
2236 { "Always On", -1 },
2237 { "Normal", -1 },
2240 do {
2241 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2242 switch(selection) {
2243 case 0:
2244 rb->set_bool("Show FPS", &show_fps);
2245 reset_track_list();
2246 break;
2248 case 1:
2249 rb->set_int("Spacing between slides", "", 1,
2250 &slide_spacing,
2251 NULL, 1, 0, 100, NULL );
2252 recalc_offsets();
2253 reset_slides();
2254 break;
2256 case 2:
2257 rb->set_int("Centre margin", "", 1,
2258 &center_margin,
2259 NULL, 1, 0, 80, NULL );
2260 recalc_offsets();
2261 reset_slides();
2262 break;
2264 case 3:
2265 rb->set_int("Number of slides", "", 1, &num_slides,
2266 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2267 recalc_offsets();
2268 reset_slides();
2269 break;
2271 case 4:
2272 rb->set_int("Zoom", "", 1, &zoom,
2273 NULL, 1, 10, 300, NULL );
2274 recalc_offsets();
2275 reset_slides();
2276 break;
2277 case 5:
2278 rb->set_option("Show album title", &show_album_name,
2279 INT, album_name_options, 3, NULL);
2280 reset_track_list();
2281 recalc_offsets();
2282 reset_slides();
2283 break;
2284 case 6:
2285 old_val = resize;
2286 rb->set_bool("Resize Covers", &resize);
2287 if (old_val == resize) /* changed? */
2288 break;
2289 /* fallthrough if changed, since cache needs to be rebuilt */
2290 case 7:
2291 cache_version = CACHE_REBUILD;
2292 rb->remove(EMPTY_SLIDE);
2293 configfile_save(CONFIG_FILE, config,
2294 CONFIG_NUM_ITEMS, CONFIG_VERSION);
2295 rb->splash(HZ, "Cache will be rebuilt on next restart");
2296 break;
2297 case 8:
2298 rb->set_option("WPS Integration", &auto_wps, INT, wps_options, 3, NULL);
2299 break;
2300 case 9:
2301 rb->set_option("Backlight", &backlight_mode, INT, backlight_options, 2, NULL);
2302 break;
2304 case MENU_ATTACHED_USB:
2305 return PLUGIN_USB_CONNECTED;
2307 } while ( selection >= 0 );
2308 return 0;
2312 Show the main menu
2314 enum {
2315 PF_GOTO_WPS,
2316 #if PF_PLAYBACK_CAPABLE
2317 PF_MENU_CLEAR_PLAYLIST,
2318 PF_MENU_PLAYBACK_CONTROL,
2319 #endif
2320 PF_MENU_SETTINGS,
2321 PF_MENU_RETURN,
2322 PF_MENU_QUIT,
2325 int main_menu(void)
2327 int selection = 0;
2328 int result;
2330 #if LCD_DEPTH > 1
2331 rb->lcd_set_foreground(N_BRIGHT(255));
2332 #endif
2334 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2335 "Go to WPS",
2336 #if PF_PLAYBACK_CAPABLE
2337 "Clear playlist", "Playback Control",
2338 #endif
2339 "Settings", "Return", "Quit");
2340 while (1) {
2341 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2342 case PF_GOTO_WPS: /* WPS */
2343 return -2;
2344 #if PF_PLAYBACK_CAPABLE
2345 case PF_MENU_CLEAR_PLAYLIST:
2346 if(rb->playlist_remove_all_tracks(NULL) == 0) {
2347 rb->playlist_create(NULL, NULL);
2348 rb->splash(HZ*2, "Playlist Cleared");
2350 break;
2351 case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */
2352 playback_control(NULL);
2353 break;
2354 #endif
2355 case PF_MENU_SETTINGS:
2356 result = settings_menu();
2357 if ( result != 0 ) return result;
2358 break;
2359 case PF_MENU_RETURN:
2360 return 0;
2361 case PF_MENU_QUIT:
2362 return -1;
2364 case MENU_ATTACHED_USB:
2365 return PLUGIN_USB_CONNECTED;
2367 default:
2368 return 0;
2374 Animation step for zooming into the current cover
2376 void update_cover_in_animation(void)
2378 cover_animation_keyframe++;
2379 if( cover_animation_keyframe < 20 ) {
2380 center_slide.distance-=5;
2381 center_slide.angle+=1;
2382 extra_fade += 13;
2384 else if( cover_animation_keyframe < 35 ) {
2385 center_slide.angle+=16;
2387 else {
2388 cover_animation_keyframe = 0;
2389 pf_state = pf_show_tracks;
2394 Animation step for zooming out the current cover
2396 void update_cover_out_animation(void)
2398 cover_animation_keyframe++;
2399 if( cover_animation_keyframe <= 15 ) {
2400 center_slide.angle-=16;
2402 else if( cover_animation_keyframe < 35 ) {
2403 center_slide.distance+=5;
2404 center_slide.angle-=1;
2405 extra_fade -= 13;
2407 else {
2408 cover_animation_keyframe = 0;
2409 pf_state = pf_idle;
2414 Draw a blue gradient at y with height h
2416 static inline void draw_gradient(int y, int h)
2418 int r, inc, c;
2419 inc = (100 << 8) / h;
2420 c = 0;
2421 selected_track_pulse = (selected_track_pulse+1) % 10;
2422 int c2 = selected_track_pulse - 5;
2423 for (r=0; r<h; r++) {
2424 #ifdef HAVE_LCD_COLOR
2425 mylcd_set_foreground(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2426 c2+250-(c >> 8)));
2427 #else
2428 mylcd_set_foreground(G_BRIGHT(c2+160-(c >> 8)));
2429 #endif
2430 mylcd_hline(0, LCD_WIDTH, r+y);
2431 if ( r > h/2 )
2432 c-=inc;
2433 else
2434 c+=inc;
2439 static void track_list_yh(int char_height)
2441 switch (show_album_name)
2443 case ALBUM_NAME_HIDE:
2444 track_list_y = (show_fps ? char_height : 0);
2445 track_list_h = LCD_HEIGHT - track_list_y;
2446 break;
2447 case ALBUM_NAME_BOTTOM:
2448 track_list_y = (show_fps ? char_height : 0);
2449 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2450 break;
2451 case ALBUM_NAME_TOP:
2452 default:
2453 track_list_y = char_height * 2;
2454 track_list_h = LCD_HEIGHT - track_list_y -
2455 (show_fps ? char_height : 0);
2456 break;
2461 Reset the track list after a album change
2463 void reset_track_list(void)
2465 int char_height = rb->screens[SCREEN_MAIN]->getcharheight();
2466 int total_height;
2467 track_list_yh(char_height);
2468 track_list_visible_entries = fmin( track_list_h/char_height , track_count );
2469 start_index_track_list = 0;
2470 selected_track = 0;
2471 last_selected_track = -1;
2473 /* let the tracklist start more centered
2474 * if the screen isn't filled with tracks */
2475 total_height = track_count*char_height;
2476 if (total_height < track_list_h)
2478 track_list_y += (track_list_h - total_height) / 2;
2479 track_list_h = total_height;
2484 Display the list of tracks
2486 void show_track_list(void)
2488 mylcd_clear_display();
2489 if ( center_slide.slide_index != track_index ) {
2490 create_track_index(center_slide.slide_index);
2491 reset_track_list();
2493 int titletxt_w, titletxt_x, color, titletxt_h;
2494 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2496 int titletxt_y = track_list_y;
2497 int track_i;
2498 track_i = start_index_track_list;
2499 for (;track_i < track_list_visible_entries+start_index_track_list;
2500 track_i++)
2502 char *trackname = get_track_name(track_i);
2503 if ( track_i == selected_track ) {
2504 if (selected_track != last_selected_track) {
2505 set_scroll_line(trackname, PF_SCROLL_TRACK);
2506 last_selected_track = selected_track;
2508 draw_gradient(titletxt_y, titletxt_h);
2509 titletxt_x = get_scroll_line_offset(PF_SCROLL_TRACK);
2510 color = 255;
2512 else {
2513 titletxt_w = mylcd_getstringsize(trackname, NULL, NULL);
2514 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2515 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2517 mylcd_set_foreground(G_BRIGHT(color));
2518 mylcd_putsxy(titletxt_x,titletxt_y,trackname);
2519 titletxt_y += titletxt_h;
2523 void select_next_track(void)
2525 if ( selected_track < track_count - 1 ) {
2526 selected_track++;
2527 if (selected_track==(track_list_visible_entries+start_index_track_list))
2528 start_index_track_list++;
2532 void select_prev_track(void)
2534 if (selected_track > 0 ) {
2535 if (selected_track==start_index_track_list) start_index_track_list--;
2536 selected_track--;
2540 #if PF_PLAYBACK_CAPABLE
2542 * Puts the current tracklist into a newly created playlist and starts playling
2544 void start_playback(bool append)
2546 static int old_playlist = -1, old_shuffle = 0;
2547 int count = 0;
2548 int position = selected_track;
2549 int shuffle = rb->global_settings->playlist_shuffle;
2550 /* reuse existing playlist if possible
2551 * regenerate if shuffle is on or changed, since playlist index and
2552 * selected track are "out of sync" */
2553 if (!shuffle && !append && center_slide.slide_index == old_playlist
2554 && (old_shuffle == shuffle))
2556 goto play;
2558 /* First, replace the current playlist with a new one */
2559 else if (append || (rb->playlist_remove_all_tracks(NULL) == 0
2560 && rb->playlist_create(NULL, NULL) == 0))
2562 do {
2563 rb->yield();
2564 if (rb->playlist_insert_track(NULL, get_track_filename(count),
2565 PLAYLIST_INSERT_LAST, false, true) < 0)
2566 break;
2567 } while(++count < track_count);
2568 rb->playlist_sync(NULL);
2570 else
2571 return;
2573 if (rb->global_settings->playlist_shuffle)
2574 position = rb->playlist_shuffle(*rb->current_tick, selected_track);
2575 play:
2576 /* TODO: can we adjust selected_track if !play_selected ?
2577 * if shuffle, we can't predict the playing track easily, and for either
2578 * case the track list doesn't get auto scrolled*/
2579 if(!append)
2580 rb->playlist_start(position, 0);
2581 old_playlist = center_slide.slide_index;
2582 old_shuffle = shuffle;
2584 #endif
2587 Draw the current album name
2589 void draw_album_text(void)
2591 if (show_album_name == ALBUM_NAME_HIDE)
2592 return;
2594 int albumtxt_index;
2595 int char_height;
2596 int albumtxt_x, albumtxt_y;
2598 char *albumtxt;
2599 int c;
2600 /* Draw album text */
2601 if ( pf_state == pf_scrolling ) {
2602 c = ((slide_frame & 0xffff )/ 255);
2603 if (step < 0) c = 255-c;
2604 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2605 albumtxt_index = center_index+step;
2606 c = (c-128)*2;
2608 else {
2609 albumtxt_index = center_index;
2610 c = (128-c)*2;
2613 else {
2614 albumtxt_index = center_index;
2615 c= 255;
2617 albumtxt = get_album_name(albumtxt_index);
2619 mylcd_set_foreground(G_BRIGHT(c));
2620 if (albumtxt_index != prev_albumtxt_index) {
2621 set_scroll_line(albumtxt, PF_SCROLL_ALBUM);
2622 prev_albumtxt_index = albumtxt_index;
2625 char_height = rb->screens[SCREEN_MAIN]->getcharheight();
2626 if (show_album_name == ALBUM_NAME_TOP)
2627 albumtxt_y = char_height / 2;
2628 else
2629 albumtxt_y = LCD_HEIGHT - char_height - char_height/2;
2631 albumtxt_x = get_scroll_line_offset(PF_SCROLL_ALBUM);
2632 mylcd_putsxy(albumtxt_x, albumtxt_y, albumtxt);
2636 Display an error message and wait for input.
2638 void error_wait(const char *message)
2640 rb->splashf(0, "%s. Press any button to continue.", message);
2641 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2642 rb->yield();
2643 rb->sleep(2 * HZ);
2647 Main function that also contain the main plasma
2648 algorithm.
2650 int main(void)
2652 int ret;
2654 rb->lcd_setfont(FONT_UI);
2656 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2657 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2658 error_wait("Could not create directory " CACHE_PREFIX);
2659 return PLUGIN_ERROR;
2663 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2664 if(auto_wps == 0)
2665 draw_splashscreen();
2666 if(backlight_mode == 0) {
2667 /* Turn off backlight timeout */
2668 backlight_ignore_timeout();
2671 init_scroll_lines();
2672 init_reflect_table();
2674 ALIGN_BUFFER(buf, buf_size, 4);
2675 ret = create_album_index();
2676 if (ret == ERROR_BUFFER_FULL) {
2677 error_wait("Not enough memory for album names");
2678 return PLUGIN_ERROR;
2679 } else if (ret == ERROR_NO_ALBUMS) {
2680 error_wait("No albums found. Please enable database");
2681 return PLUGIN_ERROR;
2684 ALIGN_BUFFER(buf, buf_size, 4);
2685 number_of_slides = album_count;
2686 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2687 cache_version = CACHE_REBUILD;
2688 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2689 error_wait("Could not create album art cache");
2690 return PLUGIN_ERROR;
2693 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2694 cache_version = CACHE_REBUILD;
2695 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2696 error_wait("Could not load the empty slide");
2697 return PLUGIN_ERROR;
2699 if (cache_version != CACHE_VERSION)
2701 cache_version = CACHE_VERSION;
2702 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2705 buflib_init(&buf_ctx, (void *)buf, buf_size);
2707 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2709 error_wait("Unable to load empty slide image");
2710 return PLUGIN_ERROR;
2713 if (!create_pf_thread()) {
2714 error_wait("Cannot create thread!");
2715 return PLUGIN_ERROR;
2718 int i;
2720 /* initialize */
2721 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2722 cache[i].hid = 0;
2723 cache[i].index = 0;
2724 cache[i].next = i + 1;
2725 cache[i].prev = i - 1;
2727 cache[0].prev = i - 1;
2728 cache[i - 1].next = 0;
2729 cache_free = 0;
2730 buffer = LCD_BUF;
2732 pf_state = pf_idle;
2734 track_index = -1;
2735 extra_fade = 0;
2736 slide_frame = 0;
2737 step = 0;
2738 target = 0;
2739 fade = 256;
2741 recalc_offsets();
2742 reset_slides();
2743 set_current_slide(get_wps_current_index());
2745 char fpstxt[10];
2746 int button;
2748 int frames = 0;
2749 long last_update = *rb->current_tick;
2750 long current_update;
2751 long update_interval = 100;
2752 int fps = 0;
2753 int fpstxt_y;
2755 bool instant_update;
2756 #ifdef USEGSLIB
2757 grey_show(true);
2758 grey_set_drawmode(DRMODE_FG);
2759 #endif
2760 rb->lcd_set_drawmode(DRMODE_FG);
2761 while (true) {
2762 current_update = *rb->current_tick;
2763 frames++;
2765 /* Initial rendering */
2766 instant_update = false;
2768 update_scroll_lines();
2770 /* Handle states */
2771 switch ( pf_state ) {
2772 case pf_scrolling:
2773 update_scroll_animation();
2774 render_all_slides();
2775 instant_update = true;
2776 break;
2777 case pf_cover_in:
2778 update_cover_in_animation();
2779 render_all_slides();
2780 instant_update = true;
2781 break;
2782 case pf_cover_out:
2783 update_cover_out_animation();
2784 render_all_slides();
2785 instant_update = true;
2786 break;
2787 case pf_show_tracks:
2788 show_track_list();
2789 break;
2790 case pf_idle:
2791 render_all_slides();
2792 break;
2795 /* Calculate FPS */
2796 if (current_update - last_update > update_interval) {
2797 fps = frames * HZ / (current_update - last_update);
2798 last_update = current_update;
2799 frames = 0;
2801 /* Draw FPS */
2802 if (show_fps)
2804 #ifdef USEGSLIB
2805 mylcd_set_foreground(G_BRIGHT(255));
2806 #else
2807 mylcd_set_foreground(G_PIX(255,0,0));
2808 #endif
2809 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2810 if (show_album_name == ALBUM_NAME_TOP)
2811 fpstxt_y = LCD_HEIGHT -
2812 rb->screens[SCREEN_MAIN]->getcharheight();
2813 else
2814 fpstxt_y = 0;
2815 mylcd_putsxy(0, fpstxt_y, fpstxt);
2817 draw_album_text();
2820 /* Copy offscreen buffer to LCD and give time to other threads */
2821 mylcd_update();
2822 rb->yield();
2824 /*/ Handle buttons */
2825 button = rb->get_custom_action(CONTEXT_PLUGIN
2826 #ifndef USE_CORE_PREVNEXT
2827 |(pf_state == pf_show_tracks ? 1 : 0)
2828 #endif
2829 ,instant_update ? 0 : HZ/16,
2830 get_context_map);
2832 switch (button) {
2833 case PF_QUIT:
2834 return PLUGIN_OK;
2835 case PF_WPS:
2836 return PLUGIN_GOTO_WPS;
2837 case PF_BACK:
2838 if ( pf_state == pf_show_tracks )
2840 buflib_buffer_in(&buf_ctx, borrowed);
2841 borrowed = 0;
2842 track_index = -1;
2843 pf_state = pf_cover_out;
2845 if (pf_state == pf_idle || pf_state == pf_scrolling)
2846 return PLUGIN_OK;
2847 break;
2848 case PF_MENU:
2849 #ifdef USEGSLIB
2850 grey_show(false);
2851 #endif
2852 ret = main_menu();
2853 if ( ret == -2 ) return PLUGIN_GOTO_WPS;
2854 if ( ret == -1 ) return PLUGIN_OK;
2855 if ( ret != 0 ) return ret;
2856 #ifdef USEGSLIB
2857 grey_show(true);
2858 #endif
2859 mylcd_set_drawmode(DRMODE_FG);
2860 break;
2862 case PF_NEXT:
2863 case PF_NEXT_REPEAT:
2864 if ( pf_state == pf_show_tracks )
2865 select_next_track();
2866 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2867 show_next_slide();
2868 break;
2870 case PF_PREV:
2871 case PF_PREV_REPEAT:
2872 if ( pf_state == pf_show_tracks )
2873 select_prev_track();
2874 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2875 show_previous_slide();
2876 break;
2877 #if PF_PLAYBACK_CAPABLE
2878 case PF_CONTEXT:
2879 if ( auto_wps != 0 ) {
2880 if( pf_state == pf_idle ) {
2881 create_track_index(center_slide.slide_index);
2882 reset_track_list();
2883 start_playback(true);
2884 rb->splash(HZ*2, "Added to playlist");
2886 else if( pf_state == pf_show_tracks ) {
2887 rb->playlist_insert_track(NULL, get_track_filename(selected_track),
2888 PLAYLIST_INSERT_LAST, false, true);
2889 rb->playlist_sync(NULL);
2890 rb->splash(HZ*2, "Added to playlist");
2893 break;
2894 #endif
2895 case PF_TRACKLIST:
2896 if ( auto_wps == 1 && pf_state == pf_idle ) {
2897 pf_state = pf_cover_in;
2898 break;
2900 case PF_SELECT:
2901 if ( pf_state == pf_idle ) {
2902 #if PF_PLAYBACK_CAPABLE
2903 if(auto_wps == 1) {
2904 create_track_index(center_slide.slide_index);
2905 reset_track_list();
2906 start_playback(false);
2907 last_album = center_index;
2908 return PLUGIN_GOTO_WPS;
2910 else
2911 #endif
2912 pf_state = pf_cover_in;
2914 else if ( pf_state == pf_show_tracks ) {
2915 #if PF_PLAYBACK_CAPABLE
2916 start_playback(false);
2917 if(auto_wps != 0) {
2918 last_album = center_index;
2919 return PLUGIN_GOTO_WPS;
2921 #endif
2923 break;
2924 default:
2925 exit_on_usb(button);
2926 break;
2931 /*************************** Plugin entry point ****************************/
2933 enum plugin_status plugin_start(const void *parameter)
2935 int ret;
2936 (void) parameter;
2937 atexit(cleanup);
2939 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2940 rb->cpu_boost(true);
2941 #endif
2942 #if PF_PLAYBACK_CAPABLE
2943 buf = rb->plugin_get_buffer(&buf_size);
2944 #else
2945 buf = rb->plugin_get_audio_buffer(&buf_size);
2946 #ifndef SIMULATOR
2947 if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
2949 uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
2950 buf_size = MIN(buf_size, tmp_size);
2952 #endif
2953 #endif
2955 #ifdef USEGSLIB
2956 long grey_buf_used;
2957 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2958 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2960 error_wait("Greylib init failed!");
2961 return PLUGIN_ERROR;
2963 grey_setfont(FONT_UI);
2964 buf_size -= grey_buf_used;
2965 buf = (void*)(grey_buf_used + (char*)buf);
2966 #endif
2968 ret = main();
2969 if ( ret == PLUGIN_OK || ret == PLUGIN_GOTO_WPS) {
2970 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2971 CONFIG_VERSION))
2973 rb->splash(HZ, "Error writing config.");
2974 ret = PLUGIN_ERROR;
2977 return ret;