Rework m4a seek/resume code. Seek/resume does now also work properly with files havin...
[kugel-rb.git] / apps / plugins / pictureflow / pictureflow.c
blobcaae9dc1630cebead3d0c41eefd4462cd667c91f
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_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 int ret;
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 ret = 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(, MAX(PRIORITY_USER_INTERFACE / 2,
1335 PRIORITY_REALTIME + 1))
1336 IF_COP(, CPU)
1338 ) == 0) {
1339 return false;
1341 thread_is_running = true;
1342 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1343 return true;
1348 * The following functions implement the linked-list-in-array used to manage
1349 * the LRU cache of slides, and the list of free cache slots.
1352 #define seek_right_while(start, cond) \
1353 ({ \
1354 int ind_, next_ = (start); \
1355 do { \
1356 ind_ = next_; \
1357 next_ = cache[ind_].next; \
1358 } while (next_ != cache_used && (cond)); \
1359 ind_; \
1362 #define seek_left_while(start, cond) \
1363 ({ \
1364 int ind_, next_ = (start); \
1365 do { \
1366 ind_ = next_; \
1367 next_ = cache[ind_].prev; \
1368 } while (ind_ != cache_used && (cond)); \
1369 ind_; \
1373 Pop the given item from the linked list starting at *head, returning the next
1374 item, or -1 if the list is now empty.
1376 static inline int lla_pop_item (int *head, int i)
1378 int prev = cache[i].prev;
1379 int next = cache[i].next;
1380 if (i == next)
1382 *head = -1;
1383 return -1;
1385 else if (i == *head)
1386 *head = next;
1387 cache[next].prev = prev;
1388 cache[prev].next = next;
1389 return next;
1394 Pop the head item from the list starting at *head, returning the index of the
1395 item, or -1 if the list is already empty.
1397 static inline int lla_pop_head (int *head)
1399 int i = *head;
1400 if (i != -1)
1401 lla_pop_item(head, i);
1402 return i;
1406 Insert the item at index i before the one at index p.
1408 static inline void lla_insert (int i, int p)
1410 int next = p;
1411 int prev = cache[next].prev;
1412 cache[next].prev = i;
1413 cache[prev].next = i;
1414 cache[i].next = next;
1415 cache[i].prev = prev;
1420 Insert the item at index i at the end of the list starting at *head.
1422 static inline void lla_insert_tail (int *head, int i)
1424 if (*head == -1)
1426 *head = i;
1427 cache[i].next = i;
1428 cache[i].prev = i;
1429 } else
1430 lla_insert(i, *head);
1434 Insert the item at index i before the one at index p.
1436 static inline void lla_insert_after(int i, int p)
1438 p = cache[p].next;
1439 lla_insert(i, p);
1444 Insert the item at index i before the one at index p in the list starting at
1445 *head
1447 static inline void lla_insert_before(int *head, int i, int p)
1449 lla_insert(i, p);
1450 if (*head == p)
1451 *head = i;
1456 Free the used slide at index i, and its buffer, and move it to the free
1457 slides list.
1459 static inline void free_slide(int i)
1461 if (cache[i].hid != empty_slide_hid)
1462 buflib_free(&buf_ctx, cache[i].hid);
1463 cache[i].index = -1;
1464 lla_pop_item(&cache_used, i);
1465 lla_insert_tail(&cache_free, i);
1466 if (cache_used == -1)
1468 cache_right_index = -1;
1469 cache_left_index = -1;
1470 cache_center_index = -1;
1476 Free one slide ranked above the given priority. If no such slide can be found,
1477 return false.
1479 static bool free_slide_prio(int prio)
1481 if (cache_used == -1)
1482 return false;
1483 int i, l = cache_used, r = cache[cache_used].prev, prio_max;
1484 int prio_l = cache[l].index < center_index ?
1485 center_index - cache[l].index : 0;
1486 int prio_r = cache[r].index > center_index ?
1487 cache[r].index - center_index : 0;
1488 if (prio_l > prio_r)
1490 i = l;
1491 prio_max = prio_l;
1492 } else {
1493 i = r;
1494 prio_max = prio_r;
1496 if (prio_max > prio)
1498 if (i == cache_left_index)
1499 cache_left_index = cache[i].next;
1500 if (i == cache_right_index)
1501 cache_right_index = cache[i].prev;
1502 free_slide(i);
1503 return true;
1504 } else
1505 return false;
1509 Read the pfraw image given as filename and return the hid of the buffer
1511 int read_pfraw(char* filename, int prio)
1513 struct pfraw_header bmph;
1514 int fh = rb->open(filename, O_RDONLY);
1515 if( fh < 0 ) {
1516 cache_version = CACHE_UPDATE;
1517 return empty_slide_hid;
1519 else
1520 rb->read(fh, &bmph, sizeof(struct pfraw_header));
1522 int size = sizeof(struct dim) +
1523 sizeof( pix_t ) * bmph.width * bmph.height;
1525 int hid;
1526 while (!(hid = buflib_alloc(&buf_ctx, size)) && free_slide_prio(prio));
1528 if (!hid) {
1529 rb->close( fh );
1530 return 0;
1533 rb->yield(); /* allow audio to play when fast scrolling */
1534 struct dim *bm = buflib_get_data(&buf_ctx, hid);
1536 bm->width = bmph.width;
1537 bm->height = bmph.height;
1538 pix_t *data = (pix_t*)(sizeof(struct dim) + (char *)bm);
1540 int y;
1541 for( y = 0; y < bm->height; y++ )
1543 rb->read( fh, data , sizeof( pix_t ) * bm->width );
1544 data += bm->width;
1546 rb->close( fh );
1547 return hid;
1552 Load the surface for the given slide_index into the cache at cache_index.
1554 static inline bool load_and_prepare_surface(const int slide_index,
1555 const int cache_index,
1556 const int prio)
1558 char pfraw_file[MAX_PATH];
1559 rb->snprintf(pfraw_file, sizeof(pfraw_file), CACHE_PREFIX "/%x.pfraw",
1560 mfnv(get_album_name(slide_index)));
1562 int hid = read_pfraw(pfraw_file, prio);
1563 if (!hid)
1564 return false;
1566 cache[cache_index].hid = hid;
1568 if ( cache_index < SLIDE_CACHE_SIZE ) {
1569 cache[cache_index].index = slide_index;
1572 return true;
1577 Load the "next" slide that we can load, freeing old slides if needed, provided
1578 that they are further from center_index than the current slide
1580 bool load_new_slide(void)
1582 int i = -1;
1583 if (cache_center_index != -1)
1585 int next, prev;
1586 if (cache[cache_center_index].index != center_index)
1588 if (cache[cache_center_index].index < center_index)
1590 cache_center_index = seek_right_while(cache_center_index,
1591 cache[next_].index <= center_index);
1592 prev = cache_center_index;
1593 next = cache[cache_center_index].next;
1595 else
1597 cache_center_index = seek_left_while(cache_center_index,
1598 cache[next_].index >= center_index);
1599 next = cache_center_index;
1600 prev = cache[cache_center_index].prev;
1602 if (cache[cache_center_index].index != center_index)
1604 if (cache_free == -1)
1605 free_slide_prio(0);
1606 i = lla_pop_head(&cache_free);
1607 if (!load_and_prepare_surface(center_index, i, 0))
1608 goto fail_and_refree;
1609 if (cache[next].index == -1)
1611 if (cache[prev].index == -1)
1612 goto insert_first_slide;
1613 else
1614 next = cache[prev].next;
1616 lla_insert(i, next);
1617 if (cache[i].index < cache[cache_used].index)
1618 cache_used = i;
1619 cache_center_index = i;
1620 cache_left_index = i;
1621 cache_right_index = i;
1622 return true;
1625 if (cache[cache_left_index].index >
1626 cache[cache_center_index].index)
1627 cache_left_index = cache_center_index;
1628 if (cache[cache_right_index].index <
1629 cache[cache_center_index].index)
1630 cache_right_index = cache_center_index;
1631 cache_left_index = seek_left_while(cache_left_index,
1632 cache[ind_].index - 1 == cache[next_].index);
1633 cache_right_index = seek_right_while(cache_right_index,
1634 cache[ind_].index - 1 == cache[next_].index);
1635 int prio_l = cache[cache_center_index].index -
1636 cache[cache_left_index].index + 1;
1637 int prio_r = cache[cache_right_index].index -
1638 cache[cache_center_index].index + 1;
1639 if ((prio_l < prio_r ||
1640 cache[cache_right_index].index >= number_of_slides) &&
1641 cache[cache_left_index].index > 0)
1643 if (cache_free == -1 && !free_slide_prio(prio_l))
1644 return false;
1645 i = lla_pop_head(&cache_free);
1646 if (load_and_prepare_surface(cache[cache_left_index].index
1647 - 1, i, prio_l))
1649 lla_insert_before(&cache_used, i, cache_left_index);
1650 cache_left_index = i;
1651 return true;
1653 } else if(cache[cache_right_index].index < number_of_slides - 1)
1655 if (cache_free == -1 && !free_slide_prio(prio_r))
1656 return false;
1657 i = lla_pop_head(&cache_free);
1658 if (load_and_prepare_surface(cache[cache_right_index].index
1659 + 1, i, prio_r))
1661 lla_insert_after(i, cache_right_index);
1662 cache_right_index = i;
1663 return true;
1666 } else {
1667 i = lla_pop_head(&cache_free);
1668 if (load_and_prepare_surface(center_index, i, 0))
1670 insert_first_slide:
1671 cache[i].next = i;
1672 cache[i].prev = i;
1673 cache_center_index = i;
1674 cache_left_index = i;
1675 cache_right_index = i;
1676 cache_used = i;
1677 return true;
1680 fail_and_refree:
1681 if (i != -1)
1683 lla_insert_tail(&cache_free, i);
1685 return false;
1690 Get a slide from the buffer
1692 static inline struct dim *get_slide(const int hid)
1694 if (!hid)
1695 return NULL;
1697 struct dim *bmp;
1699 bmp = buflib_get_data(&buf_ctx, hid);
1701 return bmp;
1706 Return the requested surface
1708 static inline struct dim *surface(const int slide_index)
1710 if (slide_index < 0)
1711 return 0;
1712 if (slide_index >= number_of_slides)
1713 return 0;
1714 int i;
1715 if ((i = cache_used ) != -1)
1717 do {
1718 if (cache[i].index == slide_index)
1719 return get_slide(cache[i].hid);
1720 i = cache[i].next;
1721 } while (i != cache_used);
1723 return get_slide(empty_slide_hid);
1727 adjust slides so that they are in "steady state" position
1729 void reset_slides(void)
1731 center_slide.angle = 0;
1732 center_slide.cx = 0;
1733 center_slide.cy = 0;
1734 center_slide.distance = 0;
1735 center_slide.slide_index = center_index;
1737 int i;
1738 for (i = 0; i < num_slides; i++) {
1739 struct slide_data *si = &left_slides[i];
1740 si->angle = itilt;
1741 si->cx = -(offsetX + slide_spacing * i * PFREAL_ONE);
1742 si->cy = offsetY;
1743 si->slide_index = center_index - 1 - i;
1744 si->distance = 0;
1747 for (i = 0; i < num_slides; i++) {
1748 struct slide_data *si = &right_slides[i];
1749 si->angle = -itilt;
1750 si->cx = offsetX + slide_spacing * i * PFREAL_ONE;
1751 si->cy = offsetY;
1752 si->slide_index = center_index + 1 + i;
1753 si->distance = 0;
1759 Updates look-up table and other stuff necessary for the rendering.
1760 Call this when the viewport size or slide dimension is changed.
1762 * To calculate the offset that will provide the proper margin, we use the same
1763 * projection used to render the slides. The solution for xc, the slide center,
1764 * is:
1765 * xp * (zo + xs * sin(r))
1766 * xc = xp - xs * cos(r) + ───────────────────────
1768 * TODO: support moving the side slides toward or away from the camera
1770 void recalc_offsets(void)
1772 PFreal xs = PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF;
1773 PFreal zo;
1774 PFreal xp = (DISPLAY_WIDTH * PFREAL_HALF - PFREAL_HALF + center_margin *
1775 PFREAL_ONE) * zoom / 100;
1776 PFreal cosr, sinr;
1778 itilt = 70 * IANGLE_MAX / 360; /* approx. 70 degrees tilted */
1779 cosr = fcos(-itilt);
1780 sinr = fsin(-itilt);
1781 zo = CAM_DIST_R * 100 / zoom - CAM_DIST_R +
1782 fmuln(MAXSLIDE_LEFT_R, sinr, PFREAL_SHIFT - 2, 0);
1783 offsetX = xp - fmul(xs, cosr) + fmuln(xp,
1784 zo + fmuln(xs, sinr, PFREAL_SHIFT - 2, 0), PFREAL_SHIFT - 2, 0)
1785 / CAM_DIST;
1786 offsetY = DISPLAY_WIDTH / 2 * (fsin(itilt) + PFREAL_ONE / 2);
1791 Fade the given color by spreading the fb_data (ushort)
1792 to an uint, multiply and compress the result back to a ushort.
1794 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1795 static inline unsigned fade_color(pix_t c, unsigned a)
1797 unsigned int result;
1798 c = swap16(c);
1799 a = (a + 2) & 0x1fc;
1800 result = ((c & 0xf81f) * a) & 0xf81f00;
1801 result |= ((c & 0x7e0) * a) & 0x7e000;
1802 result >>= 8;
1803 return swap16(result);
1805 #elif LCD_PIXELFORMAT == RGB565
1806 static inline unsigned fade_color(pix_t c, unsigned a)
1808 unsigned int result;
1809 a = (a + 2) & 0x1fc;
1810 result = ((c & 0xf81f) * a) & 0xf81f00;
1811 result |= ((c & 0x7e0) * a) & 0x7e000;
1812 result >>= 8;
1813 return result;
1815 #else
1816 static inline unsigned fade_color(pix_t c, unsigned a)
1818 unsigned val = c;
1819 return MULUQ(val, a) >> 8;
1821 #endif
1824 * Render a single slide
1825 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1826 * on the slide from its center, zo is the slide's depth offset from the plane
1827 * of the display, r is the angle at which the slide is tilted, and xp is the
1828 * point on the display corresponding to xs on the slide, the projection
1829 * formulas are:
1831 * z * (xc + xs * cos(r))
1832 * xp = ──────────────────────
1833 * z + zo + xs * sin(r)
1835 * z * (xc - xp) - xp * zo
1836 * xs = ────────────────────────
1837 * xp * sin(r) - z * cos(r)
1839 * We use the xp projection once, to find the left edge of the slide on the
1840 * display. From there, we use the xs reverse projection to find the horizontal
1841 * offset from the slide center of each column on the screen, until we reach
1842 * the right edge of the slide, or the screen. The reverse projection can be
1843 * optimized by saving the numerator and denominator of the fraction, which can
1844 * then be incremented by (z + zo) and sin(r) respectively.
1846 void render_slide(struct slide_data *slide, const int alpha)
1848 struct dim *bmp = surface(slide->slide_index);
1849 if (!bmp) {
1850 return;
1852 if (slide->angle > 255 || slide->angle < -255)
1853 return;
1854 pix_t *src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1856 const int sw = bmp->width;
1857 const int sh = bmp->height;
1858 const PFreal slide_left = -sw * PFREAL_HALF + PFREAL_HALF;
1859 const int w = LCD_WIDTH;
1861 uint8_t reftab[REFLECT_HEIGHT]; /* on stack, which is in IRAM on several targets */
1863 if (alpha == 256) { /* opaque -> copy table */
1864 rb->memcpy(reftab, reflect_table, sizeof(reftab));
1865 } else { /* precalculate faded table */
1866 int i, lalpha;
1867 for (i = 0; i < REFLECT_HEIGHT; i++) {
1868 lalpha = reflect_table[i];
1869 reftab[i] = (MULUQ(lalpha, alpha) + 129) >> 8;
1873 PFreal cosr = fcos(slide->angle);
1874 PFreal sinr = fsin(slide->angle);
1875 PFreal zo = PFREAL_ONE * slide->distance + CAM_DIST_R * 100 / zoom
1876 - CAM_DIST_R - fmuln(MAXSLIDE_LEFT_R, fabs(sinr), PFREAL_SHIFT - 2, 0);
1877 PFreal xs = slide_left, xsnum, xsnumi, xsden, xsdeni;
1878 PFreal xp = fdiv(CAM_DIST * (slide->cx + fmul(xs, cosr)),
1879 (CAM_DIST_R + zo + fmul(xs,sinr)));
1881 /* Since we're finding the screen position of the left edge of the slide,
1882 * we round up.
1884 int xi = (fmax(DISPLAY_LEFT_R, xp) - DISPLAY_LEFT_R + PFREAL_ONE - 1)
1885 >> PFREAL_SHIFT;
1886 xp = DISPLAY_LEFT_R + xi * PFREAL_ONE;
1887 if (xi >= w) {
1888 return;
1890 xsnum = CAM_DIST * (slide->cx - xp) - fmuln(xp, zo, PFREAL_SHIFT - 2, 0);
1891 xsden = fmuln(xp, sinr, PFREAL_SHIFT - 2, 0) - CAM_DIST * cosr;
1892 xs = fdiv(xsnum, xsden);
1894 xsnumi = -CAM_DIST_R - zo;
1895 xsdeni = sinr;
1896 int x;
1897 int dy = PFREAL_ONE;
1898 for (x = xi; x < w; x++) {
1899 int column = (xs - slide_left) / PFREAL_ONE;
1900 if (column >= sw)
1901 break;
1902 if (zo || slide->angle)
1903 dy = (CAM_DIST_R + zo + fmul(xs, sinr)) / CAM_DIST;
1905 const pix_t *ptr = &src[column * bmp->height];
1907 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
1908 #define PIXELSTEP_Y 1
1909 #define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)])
1910 #else
1911 #define PIXELSTEP_Y BUFFER_WIDTH
1912 #define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
1913 #endif
1915 int p = (bmp->height-1-DISPLAY_OFFS) * PFREAL_ONE;
1916 int plim = MAX(0, p - (LCD_HEIGHT/2-1) * dy);
1917 pix_t *pixel = LCDADDR(x, (LCD_HEIGHT/2)-1 );
1919 if (alpha == 256) {
1920 while (p >= plim) {
1921 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1922 p -= dy;
1923 pixel -= PIXELSTEP_Y;
1925 } else {
1926 while (p >= plim) {
1927 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1928 p -= dy;
1929 pixel -= PIXELSTEP_Y;
1932 rb->yield(); /* allow audio to play when fast scrolling */
1933 bmp = surface(slide->slide_index); /* resync surface due to yield */
1934 src = (pix_t*)(sizeof(struct dim) + (char *)bmp);
1935 ptr = &src[column * bmp->height];
1936 p = (bmp->height-DISPLAY_OFFS) * PFREAL_ONE;
1937 plim = MIN(sh * PFREAL_ONE, p + (LCD_HEIGHT/2) * dy);
1938 int plim2 = MIN(MIN(sh + REFLECT_HEIGHT, sh * 2) * PFREAL_ONE,
1939 p + (LCD_HEIGHT/2) * dy);
1940 pixel = LCDADDR(x, (LCD_HEIGHT/2) );
1942 if (alpha == 256) {
1943 while (p < plim) {
1944 *pixel = ptr[((unsigned)p) >> PFREAL_SHIFT];
1945 p += dy;
1946 pixel += PIXELSTEP_Y;
1948 } else {
1949 while (p < plim) {
1950 *pixel = fade_color(ptr[((unsigned)p) >> PFREAL_SHIFT], alpha);
1951 p += dy;
1952 pixel += PIXELSTEP_Y;
1955 while (p < plim2) {
1956 int ty = (((unsigned)p) >> PFREAL_SHIFT) - sh;
1957 int lalpha = reftab[ty];
1958 *pixel = fade_color(ptr[sh - 1 - ty], lalpha);
1959 p += dy;
1960 pixel += PIXELSTEP_Y;
1963 if (zo || slide->angle)
1965 xsnum += xsnumi;
1966 xsden += xsdeni;
1967 xs = fdiv(xsnum, xsden);
1968 } else
1969 xs += PFREAL_ONE;
1972 /* let the music play... */
1973 rb->yield();
1974 return;
1978 Jump the the given slide_index
1980 static inline void set_current_slide(const int slide_index)
1982 int old_center_index = center_index;
1983 step = 0;
1984 center_index = fbound(slide_index, 0, number_of_slides - 1);
1985 if (old_center_index != center_index)
1986 rb->queue_post(&thread_q, EV_WAKEUP, 0);
1987 target = center_index;
1988 slide_frame = slide_index << 16;
1989 reset_slides();
1993 Start the animation for changing slides
1995 void start_animation(void)
1997 step = (target < center_slide.slide_index) ? -1 : 1;
1998 pf_state = pf_scrolling;
2002 Go to the previous slide
2004 void show_previous_slide(void)
2006 if (step == 0) {
2007 if (center_index > 0) {
2008 target = center_index - 1;
2009 start_animation();
2011 } else if ( step > 0 ) {
2012 target = center_index;
2013 step = (target <= center_slide.slide_index) ? -1 : 1;
2014 } else {
2015 target = fmax(0, center_index - 2);
2021 Go to the next slide
2023 void show_next_slide(void)
2025 if (step == 0) {
2026 if (center_index < number_of_slides - 1) {
2027 target = center_index + 1;
2028 start_animation();
2030 } else if ( step < 0 ) {
2031 target = center_index;
2032 step = (target < center_slide.slide_index) ? -1 : 1;
2033 } else {
2034 target = fmin(center_index + 2, number_of_slides - 1);
2040 Render the slides. Updates only the offscreen buffer.
2042 void render_all_slides(void)
2044 mylcd_set_background(G_BRIGHT(0));
2045 /* TODO: Optimizes this by e.g. invalidating rects */
2046 mylcd_clear_display();
2048 int nleft = num_slides;
2049 int nright = num_slides;
2051 int alpha;
2052 int index;
2053 if (step == 0) {
2054 /* no animation, boring plain rendering */
2055 for (index = nleft - 2; index >= 0; index--) {
2056 alpha = (index < nleft - 2) ? 256 : 128;
2057 alpha -= extra_fade;
2058 if (alpha > 0 )
2059 render_slide(&left_slides[index], alpha);
2061 for (index = nright - 2; index >= 0; index--) {
2062 alpha = (index < nright - 2) ? 256 : 128;
2063 alpha -= extra_fade;
2064 if (alpha > 0 )
2065 render_slide(&right_slides[index], alpha);
2067 } else {
2068 /* the first and last slide must fade in/fade out */
2070 /* if step<0 and nleft==1, left_slides[0] is fading in */
2071 alpha = ((step > 0) ? 0 : ((nleft == 1) ? 256 : 128)) - fade / 2;
2072 for (index = nleft - 1; index >= 0; index--) {
2073 if (alpha > 0)
2074 render_slide(&left_slides[index], alpha);
2075 alpha += 128;
2076 if (alpha > 256) alpha = 256;
2078 /* if step>0 and nright==1, right_slides[0] is fading in */
2079 alpha = ((step > 0) ? ((nright == 1) ? 128 : 0) : -128) + fade / 2;
2080 for (index = nright - 1; index >= 0; index--) {
2081 if (alpha > 0)
2082 render_slide(&right_slides[index], alpha);
2083 alpha += 128;
2084 if (alpha > 256) alpha = 256;
2087 alpha = 256;
2088 if (step != 0 && num_slides <= 2) /* fading out center slide */
2089 alpha = (step > 0) ? 256 - fade / 2 : 128 + fade / 2;
2090 render_slide(&center_slide, alpha);
2095 Updates the animation effect. Call this periodically from a timer.
2097 void update_scroll_animation(void)
2099 if (step == 0)
2100 return;
2102 int speed = 16384;
2103 int i;
2105 /* deaccelerate when approaching the target */
2106 if (true) {
2107 const int max = 2 * 65536;
2109 int fi = slide_frame;
2110 fi -= (target << 16);
2111 if (fi < 0)
2112 fi = -fi;
2113 fi = fmin(fi, max);
2115 int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
2116 speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
2119 slide_frame += speed * step;
2121 int index = slide_frame >> 16;
2122 int pos = slide_frame & 0xffff;
2123 int neg = 65536 - pos;
2124 int tick = (step < 0) ? neg : pos;
2125 PFreal ftick = (tick * PFREAL_ONE) >> 16;
2127 /* the leftmost and rightmost slide must fade away */
2128 fade = pos / 256;
2130 if (step < 0)
2131 index++;
2132 if (center_index != index) {
2133 center_index = index;
2134 rb->queue_post(&thread_q, EV_WAKEUP, 0);
2135 slide_frame = index << 16;
2136 center_slide.slide_index = center_index;
2137 for (i = 0; i < num_slides; i++)
2138 left_slides[i].slide_index = center_index - 1 - i;
2139 for (i = 0; i < num_slides; i++)
2140 right_slides[i].slide_index = center_index + 1 + i;
2143 center_slide.angle = (step * tick * itilt) >> 16;
2144 center_slide.cx = -step * fmul(offsetX, ftick);
2145 center_slide.cy = fmul(offsetY, ftick);
2147 if (center_index == target) {
2148 reset_slides();
2149 pf_state = pf_idle;
2150 slide_frame = center_index << 16;
2151 step = 0;
2152 fade = 256;
2153 return;
2156 for (i = 0; i < num_slides; i++) {
2157 struct slide_data *si = &left_slides[i];
2158 si->angle = itilt;
2159 si->cx =
2160 -(offsetX + slide_spacing * i * PFREAL_ONE + step
2161 * slide_spacing * ftick);
2162 si->cy = offsetY;
2165 for (i = 0; i < num_slides; i++) {
2166 struct slide_data *si = &right_slides[i];
2167 si->angle = -itilt;
2168 si->cx =
2169 offsetX + slide_spacing * i * PFREAL_ONE - step
2170 * slide_spacing * ftick;
2171 si->cy = offsetY;
2174 if (step > 0) {
2175 PFreal ftick = (neg * PFREAL_ONE) >> 16;
2176 right_slides[0].angle = -(neg * itilt) >> 16;
2177 right_slides[0].cx = fmul(offsetX, ftick);
2178 right_slides[0].cy = fmul(offsetY, ftick);
2179 } else {
2180 PFreal ftick = (pos * PFREAL_ONE) >> 16;
2181 left_slides[0].angle = (pos * itilt) >> 16;
2182 left_slides[0].cx = -fmul(offsetX, ftick);
2183 left_slides[0].cy = fmul(offsetY, ftick);
2186 /* must change direction ? */
2187 if (target < index)
2188 if (step > 0)
2189 step = -1;
2190 if (target > index)
2191 if (step < 0)
2192 step = 1;
2197 Cleanup the plugin
2199 void cleanup(void)
2201 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2202 rb->cpu_boost(false);
2203 #endif
2204 end_pf_thread();
2205 /* Turn on backlight timeout (revert to settings) */
2206 backlight_use_settings();
2208 #ifdef USEGSLIB
2209 grey_release();
2210 #endif
2214 Shows the settings menu
2216 int settings_menu(void)
2218 int selection = 0;
2219 bool old_val;
2221 MENUITEM_STRINGLIST(settings_menu, "PictureFlow Settings", NULL, "Show FPS",
2222 "Spacing", "Centre margin", "Number of slides", "Zoom",
2223 "Show album title", "Resize Covers", "Rebuild cache",
2224 "WPS Integration", "Backlight");
2226 static const struct opt_items album_name_options[] = {
2227 { "Hide album title", -1 },
2228 { "Show at the bottom", -1 },
2229 { "Show at the top", -1 }
2231 static const struct opt_items wps_options[] = {
2232 { "Off", -1 },
2233 { "Direct", -1 },
2234 { "Via Track list", -1 }
2236 static const struct opt_items backlight_options[] = {
2237 { "Always On", -1 },
2238 { "Normal", -1 },
2241 do {
2242 selection=rb->do_menu(&settings_menu,&selection, NULL, false);
2243 switch(selection) {
2244 case 0:
2245 rb->set_bool("Show FPS", &show_fps);
2246 reset_track_list();
2247 break;
2249 case 1:
2250 rb->set_int("Spacing between slides", "", 1,
2251 &slide_spacing,
2252 NULL, 1, 0, 100, NULL );
2253 recalc_offsets();
2254 reset_slides();
2255 break;
2257 case 2:
2258 rb->set_int("Centre margin", "", 1,
2259 &center_margin,
2260 NULL, 1, 0, 80, NULL );
2261 recalc_offsets();
2262 reset_slides();
2263 break;
2265 case 3:
2266 rb->set_int("Number of slides", "", 1, &num_slides,
2267 NULL, 1, 1, MAX_SLIDES_COUNT, NULL );
2268 recalc_offsets();
2269 reset_slides();
2270 break;
2272 case 4:
2273 rb->set_int("Zoom", "", 1, &zoom,
2274 NULL, 1, 10, 300, NULL );
2275 recalc_offsets();
2276 reset_slides();
2277 break;
2278 case 5:
2279 rb->set_option("Show album title", &show_album_name,
2280 INT, album_name_options, 3, NULL);
2281 reset_track_list();
2282 recalc_offsets();
2283 reset_slides();
2284 break;
2285 case 6:
2286 old_val = resize;
2287 rb->set_bool("Resize Covers", &resize);
2288 if (old_val == resize) /* changed? */
2289 break;
2290 /* fallthrough if changed, since cache needs to be rebuilt */
2291 case 7:
2292 cache_version = CACHE_REBUILD;
2293 rb->remove(EMPTY_SLIDE);
2294 configfile_save(CONFIG_FILE, config,
2295 CONFIG_NUM_ITEMS, CONFIG_VERSION);
2296 rb->splash(HZ, "Cache will be rebuilt on next restart");
2297 break;
2298 case 8:
2299 rb->set_option("WPS Integration", &auto_wps, INT, wps_options, 3, NULL);
2300 break;
2301 case 9:
2302 rb->set_option("Backlight", &backlight_mode, INT, backlight_options, 2, NULL);
2303 break;
2305 case MENU_ATTACHED_USB:
2306 return PLUGIN_USB_CONNECTED;
2308 } while ( selection >= 0 );
2309 return 0;
2313 Show the main menu
2315 enum {
2316 PF_GOTO_WPS,
2317 #if PF_PLAYBACK_CAPABLE
2318 PF_MENU_CLEAR_PLAYLIST,
2319 PF_MENU_PLAYBACK_CONTROL,
2320 #endif
2321 PF_MENU_SETTINGS,
2322 PF_MENU_RETURN,
2323 PF_MENU_QUIT,
2326 int main_menu(void)
2328 int selection = 0;
2329 int result;
2331 #if LCD_DEPTH > 1
2332 rb->lcd_set_foreground(N_BRIGHT(255));
2333 #endif
2335 MENUITEM_STRINGLIST(main_menu,"PictureFlow Main Menu",NULL,
2336 "Go to WPS",
2337 #if PF_PLAYBACK_CAPABLE
2338 "Clear playlist", "Playback Control",
2339 #endif
2340 "Settings", "Return", "Quit");
2341 while (1) {
2342 switch (rb->do_menu(&main_menu,&selection, NULL, false)) {
2343 case PF_GOTO_WPS: /* WPS */
2344 return -2;
2345 #if PF_PLAYBACK_CAPABLE
2346 case PF_MENU_CLEAR_PLAYLIST:
2347 if(rb->playlist_remove_all_tracks(NULL) == 0) {
2348 rb->playlist_create(NULL, NULL);
2349 rb->splash(HZ*2, "Playlist Cleared");
2351 break;
2352 case PF_MENU_PLAYBACK_CONTROL: /* Playback Control */
2353 playback_control(NULL);
2354 break;
2355 #endif
2356 case PF_MENU_SETTINGS:
2357 result = settings_menu();
2358 if ( result != 0 ) return result;
2359 break;
2360 case PF_MENU_RETURN:
2361 return 0;
2362 case PF_MENU_QUIT:
2363 return -1;
2365 case MENU_ATTACHED_USB:
2366 return PLUGIN_USB_CONNECTED;
2368 default:
2369 return 0;
2375 Animation step for zooming into the current cover
2377 void update_cover_in_animation(void)
2379 cover_animation_keyframe++;
2380 if( cover_animation_keyframe < 20 ) {
2381 center_slide.distance-=5;
2382 center_slide.angle+=1;
2383 extra_fade += 13;
2385 else if( cover_animation_keyframe < 35 ) {
2386 center_slide.angle+=16;
2388 else {
2389 cover_animation_keyframe = 0;
2390 pf_state = pf_show_tracks;
2395 Animation step for zooming out the current cover
2397 void update_cover_out_animation(void)
2399 cover_animation_keyframe++;
2400 if( cover_animation_keyframe <= 15 ) {
2401 center_slide.angle-=16;
2403 else if( cover_animation_keyframe < 35 ) {
2404 center_slide.distance+=5;
2405 center_slide.angle-=1;
2406 extra_fade -= 13;
2408 else {
2409 cover_animation_keyframe = 0;
2410 pf_state = pf_idle;
2415 Draw a blue gradient at y with height h
2417 static inline void draw_gradient(int y, int h)
2419 int r, inc, c;
2420 inc = (100 << 8) / h;
2421 c = 0;
2422 selected_track_pulse = (selected_track_pulse+1) % 10;
2423 int c2 = selected_track_pulse - 5;
2424 for (r=0; r<h; r++) {
2425 #ifdef HAVE_LCD_COLOR
2426 mylcd_set_foreground(G_PIX(c2+80-(c >> 9), c2+100-(c >> 9),
2427 c2+250-(c >> 8)));
2428 #else
2429 mylcd_set_foreground(G_BRIGHT(c2+160-(c >> 8)));
2430 #endif
2431 mylcd_hline(0, LCD_WIDTH, r+y);
2432 if ( r > h/2 )
2433 c-=inc;
2434 else
2435 c+=inc;
2440 static void track_list_yh(int char_height)
2442 switch (show_album_name)
2444 case ALBUM_NAME_HIDE:
2445 track_list_y = (show_fps ? char_height : 0);
2446 track_list_h = LCD_HEIGHT - track_list_y;
2447 break;
2448 case ALBUM_NAME_BOTTOM:
2449 track_list_y = (show_fps ? char_height : 0);
2450 track_list_h = LCD_HEIGHT - track_list_y - char_height * 2;
2451 break;
2452 case ALBUM_NAME_TOP:
2453 default:
2454 track_list_y = char_height * 2;
2455 track_list_h = LCD_HEIGHT - track_list_y -
2456 (show_fps ? char_height : 0);
2457 break;
2462 Reset the track list after a album change
2464 void reset_track_list(void)
2466 int char_height = rb->screens[SCREEN_MAIN]->getcharheight();
2467 int total_height;
2468 track_list_yh(char_height);
2469 track_list_visible_entries = fmin( track_list_h/char_height , track_count );
2470 start_index_track_list = 0;
2471 selected_track = 0;
2472 last_selected_track = -1;
2474 /* let the tracklist start more centered
2475 * if the screen isn't filled with tracks */
2476 total_height = track_count*char_height;
2477 if (total_height < track_list_h)
2479 track_list_y += (track_list_h - total_height) / 2;
2480 track_list_h = total_height;
2485 Display the list of tracks
2487 void show_track_list(void)
2489 mylcd_clear_display();
2490 if ( center_slide.slide_index != track_index ) {
2491 create_track_index(center_slide.slide_index);
2492 reset_track_list();
2494 int titletxt_w, titletxt_x, color, titletxt_h;
2495 titletxt_h = rb->screens[SCREEN_MAIN]->getcharheight();
2497 int titletxt_y = track_list_y;
2498 int track_i;
2499 track_i = start_index_track_list;
2500 for (;track_i < track_list_visible_entries+start_index_track_list;
2501 track_i++)
2503 char *trackname = get_track_name(track_i);
2504 if ( track_i == selected_track ) {
2505 if (selected_track != last_selected_track) {
2506 set_scroll_line(trackname, PF_SCROLL_TRACK);
2507 last_selected_track = selected_track;
2509 draw_gradient(titletxt_y, titletxt_h);
2510 titletxt_x = get_scroll_line_offset(PF_SCROLL_TRACK);
2511 color = 255;
2513 else {
2514 titletxt_w = mylcd_getstringsize(trackname, NULL, NULL);
2515 titletxt_x = (LCD_WIDTH-titletxt_w)/2;
2516 color = 250 - (abs(selected_track - track_i) * 200 / track_count);
2518 mylcd_set_foreground(G_BRIGHT(color));
2519 mylcd_putsxy(titletxt_x,titletxt_y,trackname);
2520 titletxt_y += titletxt_h;
2524 void select_next_track(void)
2526 if ( selected_track < track_count - 1 ) {
2527 selected_track++;
2528 if (selected_track==(track_list_visible_entries+start_index_track_list))
2529 start_index_track_list++;
2533 void select_prev_track(void)
2535 if (selected_track > 0 ) {
2536 if (selected_track==start_index_track_list) start_index_track_list--;
2537 selected_track--;
2541 #if PF_PLAYBACK_CAPABLE
2543 * Puts the current tracklist into a newly created playlist and starts playling
2545 void start_playback(bool append)
2547 static int old_playlist = -1, old_shuffle = 0;
2548 int count = 0;
2549 int position = selected_track;
2550 int shuffle = rb->global_settings->playlist_shuffle;
2551 /* reuse existing playlist if possible
2552 * regenerate if shuffle is on or changed, since playlist index and
2553 * selected track are "out of sync" */
2554 if (!shuffle && !append && center_slide.slide_index == old_playlist
2555 && (old_shuffle == shuffle))
2557 goto play;
2559 /* First, replace the current playlist with a new one */
2560 else if (append || (rb->playlist_remove_all_tracks(NULL) == 0
2561 && rb->playlist_create(NULL, NULL) == 0))
2563 do {
2564 rb->yield();
2565 if (rb->playlist_insert_track(NULL, get_track_filename(count),
2566 PLAYLIST_INSERT_LAST, false, true) < 0)
2567 break;
2568 } while(++count < track_count);
2569 rb->playlist_sync(NULL);
2571 else
2572 return;
2574 if (rb->global_settings->playlist_shuffle)
2575 position = rb->playlist_shuffle(*rb->current_tick, selected_track);
2576 play:
2577 /* TODO: can we adjust selected_track if !play_selected ?
2578 * if shuffle, we can't predict the playing track easily, and for either
2579 * case the track list doesn't get auto scrolled*/
2580 if(!append)
2581 rb->playlist_start(position, 0);
2582 old_playlist = center_slide.slide_index;
2583 old_shuffle = shuffle;
2585 #endif
2588 Draw the current album name
2590 void draw_album_text(void)
2592 if (show_album_name == ALBUM_NAME_HIDE)
2593 return;
2595 int albumtxt_index;
2596 int char_height;
2597 int albumtxt_x, albumtxt_y;
2599 char *albumtxt;
2600 int c;
2601 /* Draw album text */
2602 if ( pf_state == pf_scrolling ) {
2603 c = ((slide_frame & 0xffff )/ 255);
2604 if (step < 0) c = 255-c;
2605 if (c > 128 ) { /* half way to next slide .. still not perfect! */
2606 albumtxt_index = center_index+step;
2607 c = (c-128)*2;
2609 else {
2610 albumtxt_index = center_index;
2611 c = (128-c)*2;
2614 else {
2615 albumtxt_index = center_index;
2616 c= 255;
2618 albumtxt = get_album_name(albumtxt_index);
2620 mylcd_set_foreground(G_BRIGHT(c));
2621 if (albumtxt_index != prev_albumtxt_index) {
2622 set_scroll_line(albumtxt, PF_SCROLL_ALBUM);
2623 prev_albumtxt_index = albumtxt_index;
2626 char_height = rb->screens[SCREEN_MAIN]->getcharheight();
2627 if (show_album_name == ALBUM_NAME_TOP)
2628 albumtxt_y = char_height / 2;
2629 else
2630 albumtxt_y = LCD_HEIGHT - char_height - char_height/2;
2632 albumtxt_x = get_scroll_line_offset(PF_SCROLL_ALBUM);
2633 mylcd_putsxy(albumtxt_x, albumtxt_y, albumtxt);
2637 Display an error message and wait for input.
2639 void error_wait(const char *message)
2641 rb->splashf(0, "%s. Press any button to continue.", message);
2642 while (rb->get_action(CONTEXT_STD, 1) == ACTION_NONE)
2643 rb->yield();
2644 rb->sleep(2 * HZ);
2648 Main function that also contain the main plasma
2649 algorithm.
2651 int main(void)
2653 int ret;
2655 rb->lcd_setfont(FONT_UI);
2657 if ( ! rb->dir_exists( CACHE_PREFIX ) ) {
2658 if ( rb->mkdir( CACHE_PREFIX ) < 0 ) {
2659 error_wait("Could not create directory " CACHE_PREFIX);
2660 return PLUGIN_ERROR;
2664 configfile_load(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2665 if(auto_wps == 0)
2666 draw_splashscreen();
2667 if(backlight_mode == 0) {
2668 /* Turn off backlight timeout */
2669 backlight_ignore_timeout();
2672 init_scroll_lines();
2673 init_reflect_table();
2675 ALIGN_BUFFER(buf, buf_size, 4);
2676 ret = create_album_index();
2677 if (ret == ERROR_BUFFER_FULL) {
2678 error_wait("Not enough memory for album names");
2679 return PLUGIN_ERROR;
2680 } else if (ret == ERROR_NO_ALBUMS) {
2681 error_wait("No albums found. Please enable database");
2682 return PLUGIN_ERROR;
2685 ALIGN_BUFFER(buf, buf_size, 4);
2686 number_of_slides = album_count;
2687 if ((cache_version != CACHE_VERSION) && !create_albumart_cache()) {
2688 cache_version = CACHE_REBUILD;
2689 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2690 error_wait("Could not create album art cache");
2691 return PLUGIN_ERROR;
2694 if (!create_empty_slide(cache_version != CACHE_VERSION)) {
2695 cache_version = CACHE_REBUILD;
2696 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2697 error_wait("Could not load the empty slide");
2698 return PLUGIN_ERROR;
2700 if (cache_version != CACHE_VERSION)
2702 cache_version = CACHE_VERSION;
2703 configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS, CONFIG_VERSION);
2706 buflib_init(&buf_ctx, (void *)buf, buf_size);
2708 if (!(empty_slide_hid = read_pfraw(EMPTY_SLIDE, 0)))
2710 error_wait("Unable to load empty slide image");
2711 return PLUGIN_ERROR;
2714 if (!create_pf_thread()) {
2715 error_wait("Cannot create thread!");
2716 return PLUGIN_ERROR;
2719 int i;
2721 /* initialize */
2722 for (i = 0; i < SLIDE_CACHE_SIZE; i++) {
2723 cache[i].hid = 0;
2724 cache[i].index = 0;
2725 cache[i].next = i + 1;
2726 cache[i].prev = i - 1;
2728 cache[0].prev = i - 1;
2729 cache[i - 1].next = 0;
2730 cache_free = 0;
2731 buffer = LCD_BUF;
2733 pf_state = pf_idle;
2735 track_index = -1;
2736 extra_fade = 0;
2737 slide_frame = 0;
2738 step = 0;
2739 target = 0;
2740 fade = 256;
2742 recalc_offsets();
2743 reset_slides();
2744 set_current_slide(get_wps_current_index());
2746 char fpstxt[10];
2747 int button;
2749 int frames = 0;
2750 long last_update = *rb->current_tick;
2751 long current_update;
2752 long update_interval = 100;
2753 int fps = 0;
2754 int fpstxt_y;
2756 bool instant_update;
2757 #ifdef USEGSLIB
2758 grey_show(true);
2759 grey_set_drawmode(DRMODE_FG);
2760 #endif
2761 rb->lcd_set_drawmode(DRMODE_FG);
2762 while (true) {
2763 current_update = *rb->current_tick;
2764 frames++;
2766 /* Initial rendering */
2767 instant_update = false;
2769 update_scroll_lines();
2771 /* Handle states */
2772 switch ( pf_state ) {
2773 case pf_scrolling:
2774 update_scroll_animation();
2775 render_all_slides();
2776 instant_update = true;
2777 break;
2778 case pf_cover_in:
2779 update_cover_in_animation();
2780 render_all_slides();
2781 instant_update = true;
2782 break;
2783 case pf_cover_out:
2784 update_cover_out_animation();
2785 render_all_slides();
2786 instant_update = true;
2787 break;
2788 case pf_show_tracks:
2789 show_track_list();
2790 break;
2791 case pf_idle:
2792 render_all_slides();
2793 break;
2796 /* Calculate FPS */
2797 if (current_update - last_update > update_interval) {
2798 fps = frames * HZ / (current_update - last_update);
2799 last_update = current_update;
2800 frames = 0;
2802 /* Draw FPS */
2803 if (show_fps)
2805 #ifdef USEGSLIB
2806 mylcd_set_foreground(G_BRIGHT(255));
2807 #else
2808 mylcd_set_foreground(G_PIX(255,0,0));
2809 #endif
2810 rb->snprintf(fpstxt, sizeof(fpstxt), "FPS: %d", fps);
2811 if (show_album_name == ALBUM_NAME_TOP)
2812 fpstxt_y = LCD_HEIGHT -
2813 rb->screens[SCREEN_MAIN]->getcharheight();
2814 else
2815 fpstxt_y = 0;
2816 mylcd_putsxy(0, fpstxt_y, fpstxt);
2818 draw_album_text();
2821 /* Copy offscreen buffer to LCD and give time to other threads */
2822 mylcd_update();
2823 rb->yield();
2825 /*/ Handle buttons */
2826 button = rb->get_custom_action(CONTEXT_PLUGIN
2827 #ifndef USE_CORE_PREVNEXT
2828 |(pf_state == pf_show_tracks ? 1 : 0)
2829 #endif
2830 ,instant_update ? 0 : HZ/16,
2831 get_context_map);
2833 switch (button) {
2834 case PF_QUIT:
2835 return PLUGIN_OK;
2836 case PF_WPS:
2837 return PLUGIN_GOTO_WPS;
2838 case PF_BACK:
2839 if ( pf_state == pf_show_tracks )
2841 buflib_buffer_in(&buf_ctx, borrowed);
2842 borrowed = 0;
2843 track_index = -1;
2844 pf_state = pf_cover_out;
2846 if (pf_state == pf_idle || pf_state == pf_scrolling)
2847 return PLUGIN_OK;
2848 break;
2849 case PF_MENU:
2850 #ifdef USEGSLIB
2851 grey_show(false);
2852 #endif
2853 ret = main_menu();
2854 if ( ret == -2 ) return PLUGIN_GOTO_WPS;
2855 if ( ret == -1 ) return PLUGIN_OK;
2856 if ( ret != 0 ) return ret;
2857 #ifdef USEGSLIB
2858 grey_show(true);
2859 #endif
2860 mylcd_set_drawmode(DRMODE_FG);
2861 break;
2863 case PF_NEXT:
2864 case PF_NEXT_REPEAT:
2865 if ( pf_state == pf_show_tracks )
2866 select_next_track();
2867 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2868 show_next_slide();
2869 break;
2871 case PF_PREV:
2872 case PF_PREV_REPEAT:
2873 if ( pf_state == pf_show_tracks )
2874 select_prev_track();
2875 if ( pf_state == pf_idle || pf_state == pf_scrolling )
2876 show_previous_slide();
2877 break;
2878 #if PF_PLAYBACK_CAPABLE
2879 case PF_CONTEXT:
2880 if ( auto_wps != 0 ) {
2881 if( pf_state == pf_idle ) {
2882 create_track_index(center_slide.slide_index);
2883 reset_track_list();
2884 start_playback(true);
2885 rb->splash(HZ*2, "Added to playlist");
2887 else if( pf_state == pf_show_tracks ) {
2888 rb->playlist_insert_track(NULL, get_track_filename(selected_track),
2889 PLAYLIST_INSERT_LAST, false, true);
2890 rb->playlist_sync(NULL);
2891 rb->splash(HZ*2, "Added to playlist");
2894 break;
2895 #endif
2896 case PF_TRACKLIST:
2897 if ( auto_wps == 1 && pf_state == pf_idle ) {
2898 pf_state = pf_cover_in;
2899 break;
2901 case PF_SELECT:
2902 if ( pf_state == pf_idle ) {
2903 #if PF_PLAYBACK_CAPABLE
2904 if(auto_wps == 1) {
2905 create_track_index(center_slide.slide_index);
2906 reset_track_list();
2907 start_playback(false);
2908 last_album = center_index;
2909 return PLUGIN_GOTO_WPS;
2911 else
2912 #endif
2913 pf_state = pf_cover_in;
2915 else if ( pf_state == pf_show_tracks ) {
2916 #if PF_PLAYBACK_CAPABLE
2917 start_playback(false);
2918 if(auto_wps != 0) {
2919 last_album = center_index;
2920 return PLUGIN_GOTO_WPS;
2922 #endif
2924 break;
2925 default:
2926 exit_on_usb(button);
2927 break;
2932 /*************************** Plugin entry point ****************************/
2934 enum plugin_status plugin_start(const void *parameter)
2936 int ret;
2937 (void) parameter;
2938 atexit(cleanup);
2940 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2941 rb->cpu_boost(true);
2942 #endif
2943 #if PF_PLAYBACK_CAPABLE
2944 buf = rb->plugin_get_buffer(&buf_size);
2945 #else
2946 buf = rb->plugin_get_audio_buffer(&buf_size);
2947 #ifndef SIMULATOR
2948 if ((uintptr_t)buf < (uintptr_t)plugin_start_addr)
2950 uint32_t tmp_size = (uintptr_t)plugin_start_addr - (uintptr_t)buf;
2951 buf_size = MIN(buf_size, tmp_size);
2953 #endif
2954 #endif
2956 #ifdef USEGSLIB
2957 long grey_buf_used;
2958 if (!grey_init(buf, buf_size, GREY_BUFFERED|GREY_ON_COP,
2959 LCD_WIDTH, LCD_HEIGHT, &grey_buf_used))
2961 error_wait("Greylib init failed!");
2962 return PLUGIN_ERROR;
2964 grey_setfont(FONT_UI);
2965 buf_size -= grey_buf_used;
2966 buf = (void*)(grey_buf_used + (char*)buf);
2967 #endif
2969 ret = main();
2970 if ( ret == PLUGIN_OK || ret == PLUGIN_GOTO_WPS) {
2971 if (configfile_save(CONFIG_FILE, config, CONFIG_NUM_ITEMS,
2972 CONFIG_VERSION))
2974 rb->splash(HZ, "Error writing config.");
2975 ret = PLUGIN_ERROR;
2978 return ret;