1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
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"
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"
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
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
},
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
},
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
},
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
},
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
},
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
},
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
},
155 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
156 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD
|CONTEXT_REMOTE
)
158 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE
)
161 const struct button_mapping
*pf_contexts
[] =
163 #ifndef USE_CORE_PREVNEXT
164 pf_context_album_scroll
,
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
177 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
179 #endif /* LCD_DEPTH <= 1 */
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 */
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"
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 */
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 */
277 int name_idx
; /* offset to the track name */
279 #if PF_PLAYBACK_CAPABLE
280 /* offset to the filename in the string, needed for playlist generation */
292 struct load_slide_event_data
{
297 enum pf_scroll_line_type
{
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
{
327 static char* show_album_name_conf
[] =
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",
355 { TYPE_INT
, 0, MAX_MARGIN
, { .int_p
= ¢er_margin
}, "center margin",
357 { TYPE_INT
, 0, MAX_SLIDES_COUNT
, { .int_p
= &num_slides
}, "slides count",
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
;
383 static int center_index
= 0; /* index of the slide that is in the center */
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);
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
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
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
)
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
)
559 return r
+ clz_lut
[v
];
563 /* Return the maximum possible left shift for a signed int32, without
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
);
580 den
>>= PFREAL_SHIFT
- shift
;
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))))
593 #define MULUQ(a, b) ((a) * (b))
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
;
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)];
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
;
656 uint8_t *qp
= (uint8_t*)row_in
;
657 for (; dest
< end
; dest
+= ctx
->bm
->height
)
660 struct uint8_rgb
*qp
= (struct uint8_rgb
*)row_in
;
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
);
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
;
678 uint32_t *qp
= (uint32_t*)row_in
;
679 for (; dest
< end
; dest
+= ctx
->bm
->height
)
680 *dest
= SC_OUT(*qp
++, ctx
);
682 struct uint32_rgb
*qp
= (struct uint32_rgb
*)row_in
;
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);
690 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
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
);
709 yuv_to_rgb(y
, u
, v
, &r
, &g
, &b
);
713 *dest
= LCD_RGBPACK_LCD(r
, g
, b
);
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
727 output_row_32_transposed
,
728 output_row_32_transposed_fromyuv
731 .output_row_32
= output_row_32_transposed
,
736 static const struct button_mapping
* get_context_map(int context
)
738 return pf_contexts
[context
& ~CONTEXT_PLUGIN
];
742 static void init_scroll_lines(void)
745 static const char scroll_tick_table
[16] = {
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
);
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
;
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)
781 if (TIME_BEFORE(*rb
->current_tick
, scroll_line_info
.next_scroll
))
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 */
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
) {
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)
813 for (i
= 0; i
< REFLECT_HEIGHT
; 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
) );
828 rb
->tagcache_search(&tcs
, tag_album
);
829 unsigned int l
, name_idx
= 0;
831 while (rb
->tagcache_get_next(&tcs
))
833 buf_size
-= sizeof(struct album_data
);
835 album
[-album_count
].name_idx
= name_idx
;
838 /* not enough memory */
839 return ERROR_BUFFER_FULL
;
841 rb
->strcpy(buf
, tcs
.result
);
843 buf
= l
+ (char *)buf
;
844 album
[-album_count
].seek
= tcs
.result_seek
;
848 rb
->tagcache_search_finish(&tcs
);
849 ALIGN_BUFFER(buf
, buf_size
, 4);
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
];
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
;
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
;
886 int get_wps_current_index(void)
888 struct mp3entry
*id3
= rb
->audio_current_track();
889 if(id3
&& id3
->album
) {
891 for( i
=0; i
< album_count
; i
++ )
893 if(!rb
->strcmp(album_names
+ album
[i
].name_idx
, id3
->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
;
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
)
917 track_index
= slide_index
;
919 if (!rb
->tagcache_search(&tcs
, tag_title
))
922 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
924 int string_index
= 0, track_num
;
927 track_names
= (char *)buflib_buffer_out(&buf_ctx
, &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
);
945 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
946 "%d.%02d: %s", disc_num
, track_num
, tcs
.result
);
948 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
949 "%d: %s", track_num
, tcs
.result
);
954 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
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
;
980 if (!free_slide_prio(0))
983 buflib_buffer_out(&buf_ctx
, &out
);
987 struct track_data
*new_tracks
= (struct track_data
*)(out
+ (uintptr_t)tracks
);
988 unsigned int bytes
= track_count
* sizeof(struct track_data
);
990 rb
->memmove(new_tracks
, tracks
, bytes
);
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
;
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
);
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
,
1027 if ( slide_index
== -1 )
1029 rb
->strlcpy( buf
, EMPTY_SLIDE
, buflen
);
1032 if (!rb
->tagcache_search(&tcs
, tag_filename
))
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
;
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
));
1051 fd
= rb
->open(tcs
.result
, O_RDONLY
);
1052 rb
->get_metadata(&id3
, fd
, tcs
.result
);
1055 if ( search_albumart_files(&id3
, ":", buf
, buflen
) )
1061 /* did not find a matching track */
1064 rb
->tagcache_search_finish(&tcs
);
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
];
1077 ALIGN_BUFFER(buf_tmp
, buf_tmp_size
, sizeof(fb_data
));
1079 struct bitmap logo
= {
1089 int ret
= rb
->read_bmp_file(SPLASH_BMP
, &logo
, buf_tmp_size
,
1090 FORMAT_NATIVE
, NULL
);
1092 rb
->lcd_set_background(N_BRIGHT(0));
1093 rb
->lcd_set_foreground(N_BRIGHT(255));
1095 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
1097 rb
->lcd_clear_display();
1101 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
1102 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
^ DRMODE_INVERSEVID
);
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
);
1116 Draw a simple progress bar
1118 void draw_progressbar(int step
)
1121 const int bar_height
= 22;
1122 const int w
= LCD_WIDTH
- 20;
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");
1133 rb
->lcd_set_foreground(N_BRIGHT(100));
1135 rb
->lcd_drawrect(x
, y
, w
+2, bar_height
);
1137 rb
->lcd_set_foreground(N_PIX(165, 231, 82));
1140 rb
->lcd_fillrect(x
+1, y
+1, step
* w
/ album_count
, bar_height
-2);
1142 rb
->lcd_set_foreground(N_BRIGHT(255));
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;
1157 hash
= (hash
^ *str
++) * p
;
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
);
1179 for( y
= 0; y
< bm
->height
; y
++ )
1181 rb
->write( fh
, data
, sizeof( pix_t
) * bm
->width
);
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)
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
);
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
)) {
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
);
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
);
1235 if (!save_pfraw(pfraw_file
, &input_bmp
))
1237 rb
->splash(HZ
, "Could not write bmp");
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");
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
;
1261 input_bmp
.width
= DISPLAY_WIDTH
;
1262 input_bmp
.height
= DISPLAY_HEIGHT
;
1264 input_bmp
.format
= FORMAT_NATIVE
;
1266 input_bmp
.data
= (char*)buf
;
1267 ret
= scaled_read_bmp_file(EMPTY_SLIDE_BMP
, &input_bmp
,
1269 FORMAT_NATIVE
|FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
,
1270 &format_transposed
);
1271 if (!save_pfraw(EMPTY_SLIDE
, &input_bmp
))
1279 Thread used for loading and preparing bitmaps in the background
1283 long sleep_time
= 5 * HZ
;
1284 struct queue_event ev
;
1286 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
1291 /* we just woke up */
1294 if(ev
.id
!= SYS_TIMEOUT
)
1295 while ( load_new_slide() ) {
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(
1331 sizeof(thread_stack
),
1333 "Picture load thread"
1334 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE
/ 2,
1335 PRIORITY_REALTIME
+ 1))
1341 thread_is_running
= true;
1342 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
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) \
1354 int ind_, next_ = (start); \
1357 next_ = cache[ind_].next; \
1358 } while (next_ != cache_used && (cond)); \
1362 #define seek_left_while(start, cond) \
1364 int ind_, next_ = (start); \
1367 next_ = cache[ind_].prev; \
1368 } while (ind_ != cache_used && (cond)); \
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
;
1385 else if (i
== *head
)
1387 cache
[next
].prev
= prev
;
1388 cache
[prev
].next
= 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
)
1401 lla_pop_item(head
, i
);
1406 Insert the item at index i before the one at index p.
1408 static inline void lla_insert (int i
, int 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
)
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
)
1444 Insert the item at index i before the one at index p in the list starting at
1447 static inline void lla_insert_before(int *head
, int i
, int p
)
1456 Free the used slide at index i, and its buffer, and move it to the free
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,
1479 static bool free_slide_prio(int prio
)
1481 if (cache_used
== -1)
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
)
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
;
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
);
1516 cache_version
= CACHE_UPDATE
;
1517 return empty_slide_hid
;
1520 rb
->read(fh
, &bmph
, sizeof(struct pfraw_header
));
1522 int size
= sizeof(struct dim
) +
1523 sizeof( pix_t
) * bmph
.width
* bmph
.height
;
1526 while (!(hid
= buflib_alloc(&buf_ctx
, size
)) && free_slide_prio(prio
));
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
);
1541 for( y
= 0; y
< bm
->height
; y
++ )
1543 rb
->read( fh
, data
, sizeof( pix_t
) * bm
->width
);
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
,
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
);
1566 cache
[cache_index
].hid
= hid
;
1568 if ( cache_index
< SLIDE_CACHE_SIZE
) {
1569 cache
[cache_index
].index
= slide_index
;
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)
1583 if (cache_center_index
!= -1)
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
;
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)
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
;
1614 next
= cache
[prev
].next
;
1616 lla_insert(i
, next
);
1617 if (cache
[i
].index
< cache
[cache_used
].index
)
1619 cache_center_index
= i
;
1620 cache_left_index
= i
;
1621 cache_right_index
= i
;
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
))
1645 i
= lla_pop_head(&cache_free
);
1646 if (load_and_prepare_surface(cache
[cache_left_index
].index
1649 lla_insert_before(&cache_used
, i
, cache_left_index
);
1650 cache_left_index
= i
;
1653 } else if(cache
[cache_right_index
].index
< number_of_slides
- 1)
1655 if (cache_free
== -1 && !free_slide_prio(prio_r
))
1657 i
= lla_pop_head(&cache_free
);
1658 if (load_and_prepare_surface(cache
[cache_right_index
].index
1661 lla_insert_after(i
, cache_right_index
);
1662 cache_right_index
= i
;
1667 i
= lla_pop_head(&cache_free
);
1668 if (load_and_prepare_surface(center_index
, i
, 0))
1673 cache_center_index
= i
;
1674 cache_left_index
= i
;
1675 cache_right_index
= i
;
1683 lla_insert_tail(&cache_free
, i
);
1690 Get a slide from the buffer
1692 static inline struct dim
*get_slide(const int hid
)
1699 bmp
= buflib_get_data(&buf_ctx
, hid
);
1706 Return the requested surface
1708 static inline struct dim
*surface(const int slide_index
)
1710 if (slide_index
< 0)
1712 if (slide_index
>= number_of_slides
)
1715 if ((i
= cache_used
) != -1)
1718 if (cache
[i
].index
== slide_index
)
1719 return get_slide(cache
[i
].hid
);
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
;
1738 for (i
= 0; i
< num_slides
; i
++) {
1739 struct slide_data
*si
= &left_slides
[i
];
1741 si
->cx
= -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
);
1743 si
->slide_index
= center_index
- 1 - i
;
1747 for (i
= 0; i
< num_slides
; i
++) {
1748 struct slide_data
*si
= &right_slides
[i
];
1750 si
->cx
= offsetX
+ slide_spacing
* i
* PFREAL_ONE
;
1752 si
->slide_index
= center_index
+ 1 + i
;
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,
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
;
1774 PFreal xp
= (DISPLAY_WIDTH
* PFREAL_HALF
- PFREAL_HALF
+ center_margin
*
1775 PFREAL_ONE
) * zoom
/ 100;
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)
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
;
1799 a
= (a
+ 2) & 0x1fc;
1800 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1801 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
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;
1816 static inline unsigned fade_color(pix_t c
, unsigned a
)
1819 return MULUQ(val
, a
) >> 8;
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
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
);
1852 if (slide
->angle
> 255 || slide
->angle
< -255)
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 */
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,
1884 int xi
= (fmax(DISPLAY_LEFT_R
, xp
) - DISPLAY_LEFT_R
+ PFREAL_ONE
- 1)
1886 xp
= DISPLAY_LEFT_R
+ xi
* PFREAL_ONE
;
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
;
1897 int dy
= PFREAL_ONE
;
1898 for (x
= xi
; x
< w
; x
++) {
1899 int column
= (xs
- slide_left
) / PFREAL_ONE
;
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)])
1911 #define PIXELSTEP_Y BUFFER_WIDTH
1912 #define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
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 );
1921 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1923 pixel
-= PIXELSTEP_Y
;
1927 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
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) );
1944 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1946 pixel
+= PIXELSTEP_Y
;
1950 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1952 pixel
+= PIXELSTEP_Y
;
1956 int ty
= (((unsigned)p
) >> PFREAL_SHIFT
) - sh
;
1957 int lalpha
= reftab
[ty
];
1958 *pixel
= fade_color(ptr
[sh
- 1 - ty
], lalpha
);
1960 pixel
+= PIXELSTEP_Y
;
1963 if (zo
|| slide
->angle
)
1967 xs
= fdiv(xsnum
, xsden
);
1972 /* let the music play... */
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
;
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;
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)
2007 if (center_index
> 0) {
2008 target
= center_index
- 1;
2011 } else if ( step
> 0 ) {
2012 target
= center_index
;
2013 step
= (target
<= center_slide
.slide_index
) ? -1 : 1;
2015 target
= fmax(0, center_index
- 2);
2021 Go to the next slide
2023 void show_next_slide(void)
2026 if (center_index
< number_of_slides
- 1) {
2027 target
= center_index
+ 1;
2030 } else if ( step
< 0 ) {
2031 target
= center_index
;
2032 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
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
;
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
;
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
;
2065 render_slide(&right_slides
[index
], alpha
);
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
--) {
2074 render_slide(&left_slides
[index
], alpha
);
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
--) {
2082 render_slide(&right_slides
[index
], alpha
);
2084 if (alpha
> 256) 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(¢er_slide
, alpha
);
2095 Updates the animation effect. Call this periodically from a timer.
2097 void update_scroll_animation(void)
2105 /* deaccelerate when approaching the target */
2107 const int max
= 2 * 65536;
2109 int fi
= slide_frame
;
2110 fi
-= (target
<< 16);
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 */
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
) {
2150 slide_frame
= center_index
<< 16;
2156 for (i
= 0; i
< num_slides
; i
++) {
2157 struct slide_data
*si
= &left_slides
[i
];
2160 -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
+ step
2161 * slide_spacing
* ftick
);
2165 for (i
= 0; i
< num_slides
; i
++) {
2166 struct slide_data
*si
= &right_slides
[i
];
2169 offsetX
+ slide_spacing
* i
* PFREAL_ONE
- step
2170 * slide_spacing
* ftick
;
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
);
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 ? */
2201 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2202 rb
->cpu_boost(false);
2205 /* Turn on backlight timeout (revert to settings) */
2206 backlight_use_settings();
2214 Shows the settings menu
2216 int settings_menu(void)
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
[] = {
2234 { "Via Track list", -1 }
2236 static const struct opt_items backlight_options
[] = {
2237 { "Always On", -1 },
2242 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
2245 rb
->set_bool("Show FPS", &show_fps
);
2250 rb
->set_int("Spacing between slides", "", 1,
2252 NULL
, 1, 0, 100, NULL
);
2258 rb
->set_int("Centre margin", "", 1,
2260 NULL
, 1, 0, 80, NULL
);
2266 rb
->set_int("Number of slides", "", 1, &num_slides
,
2267 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
2273 rb
->set_int("Zoom", "", 1, &zoom
,
2274 NULL
, 1, 10, 300, NULL
);
2279 rb
->set_option("Show album title", &show_album_name
,
2280 INT
, album_name_options
, 3, NULL
);
2287 rb
->set_bool("Resize Covers", &resize
);
2288 if (old_val
== resize
) /* changed? */
2290 /* fallthrough if changed, since cache needs to be rebuilt */
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");
2299 rb
->set_option("WPS Integration", &auto_wps
, INT
, wps_options
, 3, NULL
);
2302 rb
->set_option("Backlight", &backlight_mode
, INT
, backlight_options
, 2, NULL
);
2305 case MENU_ATTACHED_USB
:
2306 return PLUGIN_USB_CONNECTED
;
2308 } while ( selection
>= 0 );
2317 #if PF_PLAYBACK_CAPABLE
2318 PF_MENU_CLEAR_PLAYLIST
,
2319 PF_MENU_PLAYBACK_CONTROL
,
2332 rb
->lcd_set_foreground(N_BRIGHT(255));
2335 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
2337 #if PF_PLAYBACK_CAPABLE
2338 "Clear playlist", "Playback Control",
2340 "Settings", "Return", "Quit");
2342 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
2343 case PF_GOTO_WPS
: /* WPS */
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");
2352 case PF_MENU_PLAYBACK_CONTROL
: /* Playback Control */
2353 playback_control(NULL
);
2356 case PF_MENU_SETTINGS
:
2357 result
= settings_menu();
2358 if ( result
!= 0 ) return result
;
2360 case PF_MENU_RETURN
:
2365 case MENU_ATTACHED_USB
:
2366 return PLUGIN_USB_CONNECTED
;
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;
2385 else if( cover_animation_keyframe
< 35 ) {
2386 center_slide
.angle
+=16;
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;
2409 cover_animation_keyframe
= 0;
2415 Draw a blue gradient at y with height h
2417 static inline void draw_gradient(int y
, int h
)
2420 inc
= (100 << 8) / h
;
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),
2429 mylcd_set_foreground(G_BRIGHT(c2
+160-(c
>> 8)));
2431 mylcd_hline(0, LCD_WIDTH
, r
+y
);
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
;
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;
2452 case ALBUM_NAME_TOP
:
2454 track_list_y
= char_height
* 2;
2455 track_list_h
= LCD_HEIGHT
- track_list_y
-
2456 (show_fps
? char_height
: 0);
2462 Reset the track list after a album change
2464 void reset_track_list(void)
2466 int char_height
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
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;
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
);
2494 int titletxt_w
, titletxt_x
, color
, titletxt_h
;
2495 titletxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2497 int titletxt_y
= track_list_y
;
2499 track_i
= start_index_track_list
;
2500 for (;track_i
< track_list_visible_entries
+start_index_track_list
;
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
);
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 ) {
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
--;
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;
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
))
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))
2565 if (rb
->playlist_insert_track(NULL
, get_track_filename(count
),
2566 PLAYLIST_INSERT_LAST
, false, true) < 0)
2568 } while(++count
< track_count
);
2569 rb
->playlist_sync(NULL
);
2574 if (rb
->global_settings
->playlist_shuffle
)
2575 position
= rb
->playlist_shuffle(*rb
->current_tick
, selected_track
);
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*/
2581 rb
->playlist_start(position
, 0);
2582 old_playlist
= center_slide
.slide_index
;
2583 old_shuffle
= shuffle
;
2588 Draw the current album name
2590 void draw_album_text(void)
2592 if (show_album_name
== ALBUM_NAME_HIDE
)
2597 int albumtxt_x
, albumtxt_y
;
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
;
2610 albumtxt_index
= center_index
;
2615 albumtxt_index
= center_index
;
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;
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
)
2648 Main function that also contain the main plasma
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
);
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
;
2722 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) {
2725 cache
[i
].next
= i
+ 1;
2726 cache
[i
].prev
= i
- 1;
2728 cache
[0].prev
= i
- 1;
2729 cache
[i
- 1].next
= 0;
2744 set_current_slide(get_wps_current_index());
2750 long last_update
= *rb
->current_tick
;
2751 long current_update
;
2752 long update_interval
= 100;
2756 bool instant_update
;
2759 grey_set_drawmode(DRMODE_FG
);
2761 rb
->lcd_set_drawmode(DRMODE_FG
);
2763 current_update
= *rb
->current_tick
;
2766 /* Initial rendering */
2767 instant_update
= false;
2769 update_scroll_lines();
2772 switch ( pf_state
) {
2774 update_scroll_animation();
2775 render_all_slides();
2776 instant_update
= true;
2779 update_cover_in_animation();
2780 render_all_slides();
2781 instant_update
= true;
2784 update_cover_out_animation();
2785 render_all_slides();
2786 instant_update
= true;
2788 case pf_show_tracks
:
2792 render_all_slides();
2797 if (current_update
- last_update
> update_interval
) {
2798 fps
= frames
* HZ
/ (current_update
- last_update
);
2799 last_update
= current_update
;
2806 mylcd_set_foreground(G_BRIGHT(255));
2808 mylcd_set_foreground(G_PIX(255,0,0));
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();
2816 mylcd_putsxy(0, fpstxt_y
, fpstxt
);
2821 /* Copy offscreen buffer to LCD and give time to other threads */
2825 /*/ Handle buttons */
2826 button
= rb
->get_custom_action(CONTEXT_PLUGIN
2827 #ifndef USE_CORE_PREVNEXT
2828 |(pf_state
== pf_show_tracks
? 1 : 0)
2830 ,instant_update
? 0 : HZ
/16,
2837 return PLUGIN_GOTO_WPS
;
2839 if ( pf_state
== pf_show_tracks
)
2841 buflib_buffer_in(&buf_ctx
, borrowed
);
2844 pf_state
= pf_cover_out
;
2846 if (pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2854 if ( ret
== -2 ) return PLUGIN_GOTO_WPS
;
2855 if ( ret
== -1 ) return PLUGIN_OK
;
2856 if ( ret
!= 0 ) return ret
;
2860 mylcd_set_drawmode(DRMODE_FG
);
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
)
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();
2878 #if PF_PLAYBACK_CAPABLE
2880 if ( auto_wps
!= 0 ) {
2881 if( pf_state
== pf_idle
) {
2882 create_track_index(center_slide
.slide_index
);
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");
2897 if ( auto_wps
== 1 && pf_state
== pf_idle
) {
2898 pf_state
= pf_cover_in
;
2902 if ( pf_state
== pf_idle
) {
2903 #if PF_PLAYBACK_CAPABLE
2905 create_track_index(center_slide
.slide_index
);
2907 start_playback(false);
2908 last_album
= center_index
;
2909 return PLUGIN_GOTO_WPS
;
2913 pf_state
= pf_cover_in
;
2915 else if ( pf_state
== pf_show_tracks
) {
2916 #if PF_PLAYBACK_CAPABLE
2917 start_playback(false);
2919 last_album
= center_index
;
2920 return PLUGIN_GOTO_WPS
;
2926 exit_on_usb(button
);
2932 /*************************** Plugin entry point ****************************/
2934 enum plugin_status
plugin_start(const void *parameter
)
2940 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2941 rb
->cpu_boost(true);
2943 #if PF_PLAYBACK_CAPABLE
2944 buf
= rb
->plugin_get_buffer(&buf_size
);
2946 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
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
);
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
);
2970 if ( ret
== PLUGIN_OK
|| ret
== PLUGIN_GOTO_WPS
) {
2971 if (configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
,
2974 rb
->splash(HZ
, "Error writing config.");