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_DATA_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
;
1260 input_bmp
.width
= DISPLAY_WIDTH
;
1261 input_bmp
.height
= DISPLAY_HEIGHT
;
1263 input_bmp
.format
= FORMAT_NATIVE
;
1265 input_bmp
.data
= (char*)buf
;
1266 scaled_read_bmp_file(EMPTY_SLIDE_BMP
, &input_bmp
,
1268 FORMAT_NATIVE
|FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
,
1269 &format_transposed
);
1270 if (!save_pfraw(EMPTY_SLIDE
, &input_bmp
))
1278 Thread used for loading and preparing bitmaps in the background
1282 long sleep_time
= 5 * HZ
;
1283 struct queue_event ev
;
1285 rb
->queue_wait_w_tmo(&thread_q
, &ev
, sleep_time
);
1290 /* we just woke up */
1293 if(ev
.id
!= SYS_TIMEOUT
)
1294 while ( load_new_slide() ) {
1306 End the thread by posting the EV_EXIT event
1308 void end_pf_thread(void)
1310 if ( thread_is_running
) {
1311 rb
->queue_post(&thread_q
, EV_EXIT
, 0);
1312 rb
->thread_wait(thread_id
);
1313 /* remove the thread's queue from the broadcast list */
1314 rb
->queue_delete(&thread_q
);
1315 thread_is_running
= false;
1321 Create the thread an setup the event queue
1323 bool create_pf_thread(void)
1325 /* put the thread's queue in the bcast list */
1326 rb
->queue_init(&thread_q
, true);
1327 if ((thread_id
= rb
->create_thread(
1330 sizeof(thread_stack
),
1332 "Picture load thread"
1333 IF_PRIO(, MAX(PRIORITY_USER_INTERFACE
/ 2,
1334 PRIORITY_REALTIME
+ 1))
1340 thread_is_running
= true;
1341 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1347 * The following functions implement the linked-list-in-array used to manage
1348 * the LRU cache of slides, and the list of free cache slots.
1351 #define seek_right_while(start, cond) \
1353 int ind_, next_ = (start); \
1356 next_ = cache[ind_].next; \
1357 } while (next_ != cache_used && (cond)); \
1361 #define seek_left_while(start, cond) \
1363 int ind_, next_ = (start); \
1366 next_ = cache[ind_].prev; \
1367 } while (ind_ != cache_used && (cond)); \
1372 Pop the given item from the linked list starting at *head, returning the next
1373 item, or -1 if the list is now empty.
1375 static inline int lla_pop_item (int *head
, int i
)
1377 int prev
= cache
[i
].prev
;
1378 int next
= cache
[i
].next
;
1384 else if (i
== *head
)
1386 cache
[next
].prev
= prev
;
1387 cache
[prev
].next
= next
;
1393 Pop the head item from the list starting at *head, returning the index of the
1394 item, or -1 if the list is already empty.
1396 static inline int lla_pop_head (int *head
)
1400 lla_pop_item(head
, i
);
1405 Insert the item at index i before the one at index p.
1407 static inline void lla_insert (int i
, int p
)
1410 int prev
= cache
[next
].prev
;
1411 cache
[next
].prev
= i
;
1412 cache
[prev
].next
= i
;
1413 cache
[i
].next
= next
;
1414 cache
[i
].prev
= prev
;
1419 Insert the item at index i at the end of the list starting at *head.
1421 static inline void lla_insert_tail (int *head
, int i
)
1429 lla_insert(i
, *head
);
1433 Insert the item at index i before the one at index p.
1435 static inline void lla_insert_after(int i
, int p
)
1443 Insert the item at index i before the one at index p in the list starting at
1446 static inline void lla_insert_before(int *head
, int i
, int p
)
1455 Free the used slide at index i, and its buffer, and move it to the free
1458 static inline void free_slide(int i
)
1460 if (cache
[i
].hid
!= empty_slide_hid
)
1461 buflib_free(&buf_ctx
, cache
[i
].hid
);
1462 cache
[i
].index
= -1;
1463 lla_pop_item(&cache_used
, i
);
1464 lla_insert_tail(&cache_free
, i
);
1465 if (cache_used
== -1)
1467 cache_right_index
= -1;
1468 cache_left_index
= -1;
1469 cache_center_index
= -1;
1475 Free one slide ranked above the given priority. If no such slide can be found,
1478 static bool free_slide_prio(int prio
)
1480 if (cache_used
== -1)
1482 int i
, l
= cache_used
, r
= cache
[cache_used
].prev
, prio_max
;
1483 int prio_l
= cache
[l
].index
< center_index
?
1484 center_index
- cache
[l
].index
: 0;
1485 int prio_r
= cache
[r
].index
> center_index
?
1486 cache
[r
].index
- center_index
: 0;
1487 if (prio_l
> prio_r
)
1495 if (prio_max
> prio
)
1497 if (i
== cache_left_index
)
1498 cache_left_index
= cache
[i
].next
;
1499 if (i
== cache_right_index
)
1500 cache_right_index
= cache
[i
].prev
;
1508 Read the pfraw image given as filename and return the hid of the buffer
1510 int read_pfraw(char* filename
, int prio
)
1512 struct pfraw_header bmph
;
1513 int fh
= rb
->open(filename
, O_RDONLY
);
1515 cache_version
= CACHE_UPDATE
;
1516 return empty_slide_hid
;
1519 rb
->read(fh
, &bmph
, sizeof(struct pfraw_header
));
1521 int size
= sizeof(struct dim
) +
1522 sizeof( pix_t
) * bmph
.width
* bmph
.height
;
1525 while (!(hid
= buflib_alloc(&buf_ctx
, size
)) && free_slide_prio(prio
));
1532 rb
->yield(); /* allow audio to play when fast scrolling */
1533 struct dim
*bm
= buflib_get_data(&buf_ctx
, hid
);
1535 bm
->width
= bmph
.width
;
1536 bm
->height
= bmph
.height
;
1537 pix_t
*data
= (pix_t
*)(sizeof(struct dim
) + (char *)bm
);
1540 for( y
= 0; y
< bm
->height
; y
++ )
1542 rb
->read( fh
, data
, sizeof( pix_t
) * bm
->width
);
1551 Load the surface for the given slide_index into the cache at cache_index.
1553 static inline bool load_and_prepare_surface(const int slide_index
,
1554 const int cache_index
,
1557 char pfraw_file
[MAX_PATH
];
1558 rb
->snprintf(pfraw_file
, sizeof(pfraw_file
), CACHE_PREFIX
"/%x.pfraw",
1559 mfnv(get_album_name(slide_index
)));
1561 int hid
= read_pfraw(pfraw_file
, prio
);
1565 cache
[cache_index
].hid
= hid
;
1567 if ( cache_index
< SLIDE_CACHE_SIZE
) {
1568 cache
[cache_index
].index
= slide_index
;
1576 Load the "next" slide that we can load, freeing old slides if needed, provided
1577 that they are further from center_index than the current slide
1579 bool load_new_slide(void)
1582 if (cache_center_index
!= -1)
1585 if (cache
[cache_center_index
].index
!= center_index
)
1587 if (cache
[cache_center_index
].index
< center_index
)
1589 cache_center_index
= seek_right_while(cache_center_index
,
1590 cache
[next_
].index
<= center_index
);
1591 prev
= cache_center_index
;
1592 next
= cache
[cache_center_index
].next
;
1596 cache_center_index
= seek_left_while(cache_center_index
,
1597 cache
[next_
].index
>= center_index
);
1598 next
= cache_center_index
;
1599 prev
= cache
[cache_center_index
].prev
;
1601 if (cache
[cache_center_index
].index
!= center_index
)
1603 if (cache_free
== -1)
1605 i
= lla_pop_head(&cache_free
);
1606 if (!load_and_prepare_surface(center_index
, i
, 0))
1607 goto fail_and_refree
;
1608 if (cache
[next
].index
== -1)
1610 if (cache
[prev
].index
== -1)
1611 goto insert_first_slide
;
1613 next
= cache
[prev
].next
;
1615 lla_insert(i
, next
);
1616 if (cache
[i
].index
< cache
[cache_used
].index
)
1618 cache_center_index
= i
;
1619 cache_left_index
= i
;
1620 cache_right_index
= i
;
1624 if (cache
[cache_left_index
].index
>
1625 cache
[cache_center_index
].index
)
1626 cache_left_index
= cache_center_index
;
1627 if (cache
[cache_right_index
].index
<
1628 cache
[cache_center_index
].index
)
1629 cache_right_index
= cache_center_index
;
1630 cache_left_index
= seek_left_while(cache_left_index
,
1631 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1632 cache_right_index
= seek_right_while(cache_right_index
,
1633 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1634 int prio_l
= cache
[cache_center_index
].index
-
1635 cache
[cache_left_index
].index
+ 1;
1636 int prio_r
= cache
[cache_right_index
].index
-
1637 cache
[cache_center_index
].index
+ 1;
1638 if ((prio_l
< prio_r
||
1639 cache
[cache_right_index
].index
>= number_of_slides
) &&
1640 cache
[cache_left_index
].index
> 0)
1642 if (cache_free
== -1 && !free_slide_prio(prio_l
))
1644 i
= lla_pop_head(&cache_free
);
1645 if (load_and_prepare_surface(cache
[cache_left_index
].index
1648 lla_insert_before(&cache_used
, i
, cache_left_index
);
1649 cache_left_index
= i
;
1652 } else if(cache
[cache_right_index
].index
< number_of_slides
- 1)
1654 if (cache_free
== -1 && !free_slide_prio(prio_r
))
1656 i
= lla_pop_head(&cache_free
);
1657 if (load_and_prepare_surface(cache
[cache_right_index
].index
1660 lla_insert_after(i
, cache_right_index
);
1661 cache_right_index
= i
;
1666 i
= lla_pop_head(&cache_free
);
1667 if (load_and_prepare_surface(center_index
, i
, 0))
1672 cache_center_index
= i
;
1673 cache_left_index
= i
;
1674 cache_right_index
= i
;
1682 lla_insert_tail(&cache_free
, i
);
1689 Get a slide from the buffer
1691 static inline struct dim
*get_slide(const int hid
)
1698 bmp
= buflib_get_data(&buf_ctx
, hid
);
1705 Return the requested surface
1707 static inline struct dim
*surface(const int slide_index
)
1709 if (slide_index
< 0)
1711 if (slide_index
>= number_of_slides
)
1714 if ((i
= cache_used
) != -1)
1717 if (cache
[i
].index
== slide_index
)
1718 return get_slide(cache
[i
].hid
);
1720 } while (i
!= cache_used
);
1722 return get_slide(empty_slide_hid
);
1726 adjust slides so that they are in "steady state" position
1728 void reset_slides(void)
1730 center_slide
.angle
= 0;
1731 center_slide
.cx
= 0;
1732 center_slide
.cy
= 0;
1733 center_slide
.distance
= 0;
1734 center_slide
.slide_index
= center_index
;
1737 for (i
= 0; i
< num_slides
; i
++) {
1738 struct slide_data
*si
= &left_slides
[i
];
1740 si
->cx
= -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
);
1742 si
->slide_index
= center_index
- 1 - i
;
1746 for (i
= 0; i
< num_slides
; i
++) {
1747 struct slide_data
*si
= &right_slides
[i
];
1749 si
->cx
= offsetX
+ slide_spacing
* i
* PFREAL_ONE
;
1751 si
->slide_index
= center_index
+ 1 + i
;
1758 Updates look-up table and other stuff necessary for the rendering.
1759 Call this when the viewport size or slide dimension is changed.
1761 * To calculate the offset that will provide the proper margin, we use the same
1762 * projection used to render the slides. The solution for xc, the slide center,
1764 * xp * (zo + xs * sin(r))
1765 * xc = xp - xs * cos(r) + ───────────────────────
1767 * TODO: support moving the side slides toward or away from the camera
1769 void recalc_offsets(void)
1771 PFreal xs
= PFREAL_HALF
- DISPLAY_WIDTH
* PFREAL_HALF
;
1773 PFreal xp
= (DISPLAY_WIDTH
* PFREAL_HALF
- PFREAL_HALF
+ center_margin
*
1774 PFREAL_ONE
) * zoom
/ 100;
1777 itilt
= 70 * IANGLE_MAX
/ 360; /* approx. 70 degrees tilted */
1778 cosr
= fcos(-itilt
);
1779 sinr
= fsin(-itilt
);
1780 zo
= CAM_DIST_R
* 100 / zoom
- CAM_DIST_R
+
1781 fmuln(MAXSLIDE_LEFT_R
, sinr
, PFREAL_SHIFT
- 2, 0);
1782 offsetX
= xp
- fmul(xs
, cosr
) + fmuln(xp
,
1783 zo
+ fmuln(xs
, sinr
, PFREAL_SHIFT
- 2, 0), PFREAL_SHIFT
- 2, 0)
1785 offsetY
= DISPLAY_WIDTH
/ 2 * (fsin(itilt
) + PFREAL_ONE
/ 2);
1790 Fade the given color by spreading the fb_data (ushort)
1791 to an uint, multiply and compress the result back to a ushort.
1793 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1794 static inline unsigned fade_color(pix_t c
, unsigned a
)
1796 unsigned int result
;
1798 a
= (a
+ 2) & 0x1fc;
1799 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1800 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1802 return swap16(result
);
1804 #elif LCD_PIXELFORMAT == RGB565
1805 static inline unsigned fade_color(pix_t c
, unsigned a
)
1807 unsigned int result
;
1808 a
= (a
+ 2) & 0x1fc;
1809 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1810 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1815 static inline unsigned fade_color(pix_t c
, unsigned a
)
1818 return MULUQ(val
, a
) >> 8;
1823 * Render a single slide
1824 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1825 * on the slide from its center, zo is the slide's depth offset from the plane
1826 * of the display, r is the angle at which the slide is tilted, and xp is the
1827 * point on the display corresponding to xs on the slide, the projection
1830 * z * (xc + xs * cos(r))
1831 * xp = ──────────────────────
1832 * z + zo + xs * sin(r)
1834 * z * (xc - xp) - xp * zo
1835 * xs = ────────────────────────
1836 * xp * sin(r) - z * cos(r)
1838 * We use the xp projection once, to find the left edge of the slide on the
1839 * display. From there, we use the xs reverse projection to find the horizontal
1840 * offset from the slide center of each column on the screen, until we reach
1841 * the right edge of the slide, or the screen. The reverse projection can be
1842 * optimized by saving the numerator and denominator of the fraction, which can
1843 * then be incremented by (z + zo) and sin(r) respectively.
1845 void render_slide(struct slide_data
*slide
, const int alpha
)
1847 struct dim
*bmp
= surface(slide
->slide_index
);
1851 if (slide
->angle
> 255 || slide
->angle
< -255)
1853 pix_t
*src
= (pix_t
*)(sizeof(struct dim
) + (char *)bmp
);
1855 const int sw
= bmp
->width
;
1856 const int sh
= bmp
->height
;
1857 const PFreal slide_left
= -sw
* PFREAL_HALF
+ PFREAL_HALF
;
1858 const int w
= LCD_WIDTH
;
1860 uint8_t reftab
[REFLECT_HEIGHT
]; /* on stack, which is in IRAM on several targets */
1862 if (alpha
== 256) { /* opaque -> copy table */
1863 rb
->memcpy(reftab
, reflect_table
, sizeof(reftab
));
1864 } else { /* precalculate faded table */
1866 for (i
= 0; i
< REFLECT_HEIGHT
; i
++) {
1867 lalpha
= reflect_table
[i
];
1868 reftab
[i
] = (MULUQ(lalpha
, alpha
) + 129) >> 8;
1872 PFreal cosr
= fcos(slide
->angle
);
1873 PFreal sinr
= fsin(slide
->angle
);
1874 PFreal zo
= PFREAL_ONE
* slide
->distance
+ CAM_DIST_R
* 100 / zoom
1875 - CAM_DIST_R
- fmuln(MAXSLIDE_LEFT_R
, fabs(sinr
), PFREAL_SHIFT
- 2, 0);
1876 PFreal xs
= slide_left
, xsnum
, xsnumi
, xsden
, xsdeni
;
1877 PFreal xp
= fdiv(CAM_DIST
* (slide
->cx
+ fmul(xs
, cosr
)),
1878 (CAM_DIST_R
+ zo
+ fmul(xs
,sinr
)));
1880 /* Since we're finding the screen position of the left edge of the slide,
1883 int xi
= (fmax(DISPLAY_LEFT_R
, xp
) - DISPLAY_LEFT_R
+ PFREAL_ONE
- 1)
1885 xp
= DISPLAY_LEFT_R
+ xi
* PFREAL_ONE
;
1889 xsnum
= CAM_DIST
* (slide
->cx
- xp
) - fmuln(xp
, zo
, PFREAL_SHIFT
- 2, 0);
1890 xsden
= fmuln(xp
, sinr
, PFREAL_SHIFT
- 2, 0) - CAM_DIST
* cosr
;
1891 xs
= fdiv(xsnum
, xsden
);
1893 xsnumi
= -CAM_DIST_R
- zo
;
1896 int dy
= PFREAL_ONE
;
1897 for (x
= xi
; x
< w
; x
++) {
1898 int column
= (xs
- slide_left
) / PFREAL_ONE
;
1901 if (zo
|| slide
->angle
)
1902 dy
= (CAM_DIST_R
+ zo
+ fmul(xs
, sinr
)) / CAM_DIST
;
1904 const pix_t
*ptr
= &src
[column
* bmp
->height
];
1906 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
1907 #define PIXELSTEP_Y 1
1908 #define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)])
1910 #define PIXELSTEP_Y BUFFER_WIDTH
1911 #define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
1914 int p
= (bmp
->height
-1-DISPLAY_OFFS
) * PFREAL_ONE
;
1915 int plim
= MAX(0, p
- (LCD_HEIGHT
/2-1) * dy
);
1916 pix_t
*pixel
= LCDADDR(x
, (LCD_HEIGHT
/2)-1 );
1920 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1922 pixel
-= PIXELSTEP_Y
;
1926 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1928 pixel
-= PIXELSTEP_Y
;
1931 rb
->yield(); /* allow audio to play when fast scrolling */
1932 bmp
= surface(slide
->slide_index
); /* resync surface due to yield */
1933 src
= (pix_t
*)(sizeof(struct dim
) + (char *)bmp
);
1934 ptr
= &src
[column
* bmp
->height
];
1935 p
= (bmp
->height
-DISPLAY_OFFS
) * PFREAL_ONE
;
1936 plim
= MIN(sh
* PFREAL_ONE
, p
+ (LCD_HEIGHT
/2) * dy
);
1937 int plim2
= MIN(MIN(sh
+ REFLECT_HEIGHT
, sh
* 2) * PFREAL_ONE
,
1938 p
+ (LCD_HEIGHT
/2) * dy
);
1939 pixel
= LCDADDR(x
, (LCD_HEIGHT
/2) );
1943 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1945 pixel
+= PIXELSTEP_Y
;
1949 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1951 pixel
+= PIXELSTEP_Y
;
1955 int ty
= (((unsigned)p
) >> PFREAL_SHIFT
) - sh
;
1956 int lalpha
= reftab
[ty
];
1957 *pixel
= fade_color(ptr
[sh
- 1 - ty
], lalpha
);
1959 pixel
+= PIXELSTEP_Y
;
1962 if (zo
|| slide
->angle
)
1966 xs
= fdiv(xsnum
, xsden
);
1971 /* let the music play... */
1977 Jump the the given slide_index
1979 static inline void set_current_slide(const int slide_index
)
1981 int old_center_index
= center_index
;
1983 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1984 if (old_center_index
!= center_index
)
1985 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1986 target
= center_index
;
1987 slide_frame
= slide_index
<< 16;
1992 Start the animation for changing slides
1994 void start_animation(void)
1996 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
1997 pf_state
= pf_scrolling
;
2001 Go to the previous slide
2003 void show_previous_slide(void)
2006 if (center_index
> 0) {
2007 target
= center_index
- 1;
2010 } else if ( step
> 0 ) {
2011 target
= center_index
;
2012 step
= (target
<= center_slide
.slide_index
) ? -1 : 1;
2014 target
= fmax(0, center_index
- 2);
2020 Go to the next slide
2022 void show_next_slide(void)
2025 if (center_index
< number_of_slides
- 1) {
2026 target
= center_index
+ 1;
2029 } else if ( step
< 0 ) {
2030 target
= center_index
;
2031 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
2033 target
= fmin(center_index
+ 2, number_of_slides
- 1);
2039 Render the slides. Updates only the offscreen buffer.
2041 void render_all_slides(void)
2043 mylcd_set_background(G_BRIGHT(0));
2044 /* TODO: Optimizes this by e.g. invalidating rects */
2045 mylcd_clear_display();
2047 int nleft
= num_slides
;
2048 int nright
= num_slides
;
2053 /* no animation, boring plain rendering */
2054 for (index
= nleft
- 2; index
>= 0; index
--) {
2055 alpha
= (index
< nleft
- 2) ? 256 : 128;
2056 alpha
-= extra_fade
;
2058 render_slide(&left_slides
[index
], alpha
);
2060 for (index
= nright
- 2; index
>= 0; index
--) {
2061 alpha
= (index
< nright
- 2) ? 256 : 128;
2062 alpha
-= extra_fade
;
2064 render_slide(&right_slides
[index
], alpha
);
2067 /* the first and last slide must fade in/fade out */
2069 /* if step<0 and nleft==1, left_slides[0] is fading in */
2070 alpha
= ((step
> 0) ? 0 : ((nleft
== 1) ? 256 : 128)) - fade
/ 2;
2071 for (index
= nleft
- 1; index
>= 0; index
--) {
2073 render_slide(&left_slides
[index
], alpha
);
2075 if (alpha
> 256) alpha
= 256;
2077 /* if step>0 and nright==1, right_slides[0] is fading in */
2078 alpha
= ((step
> 0) ? ((nright
== 1) ? 128 : 0) : -128) + fade
/ 2;
2079 for (index
= nright
- 1; index
>= 0; index
--) {
2081 render_slide(&right_slides
[index
], alpha
);
2083 if (alpha
> 256) alpha
= 256;
2087 if (step
!= 0 && num_slides
<= 2) /* fading out center slide */
2088 alpha
= (step
> 0) ? 256 - fade
/ 2 : 128 + fade
/ 2;
2089 render_slide(¢er_slide
, alpha
);
2094 Updates the animation effect. Call this periodically from a timer.
2096 void update_scroll_animation(void)
2104 /* deaccelerate when approaching the target */
2106 const int max
= 2 * 65536;
2108 int fi
= slide_frame
;
2109 fi
-= (target
<< 16);
2114 int ia
= IANGLE_MAX
* (fi
- max
/ 2) / (max
* 2);
2115 speed
= 512 + 16384 * (PFREAL_ONE
+ fsin(ia
)) / PFREAL_ONE
;
2118 slide_frame
+= speed
* step
;
2120 int index
= slide_frame
>> 16;
2121 int pos
= slide_frame
& 0xffff;
2122 int neg
= 65536 - pos
;
2123 int tick
= (step
< 0) ? neg
: pos
;
2124 PFreal ftick
= (tick
* PFREAL_ONE
) >> 16;
2126 /* the leftmost and rightmost slide must fade away */
2131 if (center_index
!= index
) {
2132 center_index
= index
;
2133 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
2134 slide_frame
= index
<< 16;
2135 center_slide
.slide_index
= center_index
;
2136 for (i
= 0; i
< num_slides
; i
++)
2137 left_slides
[i
].slide_index
= center_index
- 1 - i
;
2138 for (i
= 0; i
< num_slides
; i
++)
2139 right_slides
[i
].slide_index
= center_index
+ 1 + i
;
2142 center_slide
.angle
= (step
* tick
* itilt
) >> 16;
2143 center_slide
.cx
= -step
* fmul(offsetX
, ftick
);
2144 center_slide
.cy
= fmul(offsetY
, ftick
);
2146 if (center_index
== target
) {
2149 slide_frame
= center_index
<< 16;
2155 for (i
= 0; i
< num_slides
; i
++) {
2156 struct slide_data
*si
= &left_slides
[i
];
2159 -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
+ step
2160 * slide_spacing
* ftick
);
2164 for (i
= 0; i
< num_slides
; i
++) {
2165 struct slide_data
*si
= &right_slides
[i
];
2168 offsetX
+ slide_spacing
* i
* PFREAL_ONE
- step
2169 * slide_spacing
* ftick
;
2174 PFreal ftick
= (neg
* PFREAL_ONE
) >> 16;
2175 right_slides
[0].angle
= -(neg
* itilt
) >> 16;
2176 right_slides
[0].cx
= fmul(offsetX
, ftick
);
2177 right_slides
[0].cy
= fmul(offsetY
, ftick
);
2179 PFreal ftick
= (pos
* PFREAL_ONE
) >> 16;
2180 left_slides
[0].angle
= (pos
* itilt
) >> 16;
2181 left_slides
[0].cx
= -fmul(offsetX
, ftick
);
2182 left_slides
[0].cy
= fmul(offsetY
, ftick
);
2185 /* must change direction ? */
2200 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2201 rb
->cpu_boost(false);
2204 /* Turn on backlight timeout (revert to settings) */
2205 backlight_use_settings();
2213 Shows the settings menu
2215 int settings_menu(void)
2220 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
2221 "Spacing", "Centre margin", "Number of slides", "Zoom",
2222 "Show album title", "Resize Covers", "Rebuild cache",
2223 "WPS Integration", "Backlight");
2225 static const struct opt_items album_name_options
[] = {
2226 { "Hide album title", -1 },
2227 { "Show at the bottom", -1 },
2228 { "Show at the top", -1 }
2230 static const struct opt_items wps_options
[] = {
2233 { "Via Track list", -1 }
2235 static const struct opt_items backlight_options
[] = {
2236 { "Always On", -1 },
2241 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
2244 rb
->set_bool("Show FPS", &show_fps
);
2249 rb
->set_int("Spacing between slides", "", 1,
2251 NULL
, 1, 0, 100, NULL
);
2257 rb
->set_int("Centre margin", "", 1,
2259 NULL
, 1, 0, 80, NULL
);
2265 rb
->set_int("Number of slides", "", 1, &num_slides
,
2266 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
2272 rb
->set_int("Zoom", "", 1, &zoom
,
2273 NULL
, 1, 10, 300, NULL
);
2278 rb
->set_option("Show album title", &show_album_name
,
2279 INT
, album_name_options
, 3, NULL
);
2286 rb
->set_bool("Resize Covers", &resize
);
2287 if (old_val
== resize
) /* changed? */
2289 /* fallthrough if changed, since cache needs to be rebuilt */
2291 cache_version
= CACHE_REBUILD
;
2292 rb
->remove(EMPTY_SLIDE
);
2293 configfile_save(CONFIG_FILE
, config
,
2294 CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2295 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
2298 rb
->set_option("WPS Integration", &auto_wps
, INT
, wps_options
, 3, NULL
);
2301 rb
->set_option("Backlight", &backlight_mode
, INT
, backlight_options
, 2, NULL
);
2304 case MENU_ATTACHED_USB
:
2305 return PLUGIN_USB_CONNECTED
;
2307 } while ( selection
>= 0 );
2316 #if PF_PLAYBACK_CAPABLE
2317 PF_MENU_CLEAR_PLAYLIST
,
2318 PF_MENU_PLAYBACK_CONTROL
,
2331 rb
->lcd_set_foreground(N_BRIGHT(255));
2334 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
2336 #if PF_PLAYBACK_CAPABLE
2337 "Clear playlist", "Playback Control",
2339 "Settings", "Return", "Quit");
2341 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
2342 case PF_GOTO_WPS
: /* WPS */
2344 #if PF_PLAYBACK_CAPABLE
2345 case PF_MENU_CLEAR_PLAYLIST
:
2346 if(rb
->playlist_remove_all_tracks(NULL
) == 0) {
2347 rb
->playlist_create(NULL
, NULL
);
2348 rb
->splash(HZ
*2, "Playlist Cleared");
2351 case PF_MENU_PLAYBACK_CONTROL
: /* Playback Control */
2352 playback_control(NULL
);
2355 case PF_MENU_SETTINGS
:
2356 result
= settings_menu();
2357 if ( result
!= 0 ) return result
;
2359 case PF_MENU_RETURN
:
2364 case MENU_ATTACHED_USB
:
2365 return PLUGIN_USB_CONNECTED
;
2374 Animation step for zooming into the current cover
2376 void update_cover_in_animation(void)
2378 cover_animation_keyframe
++;
2379 if( cover_animation_keyframe
< 20 ) {
2380 center_slide
.distance
-=5;
2381 center_slide
.angle
+=1;
2384 else if( cover_animation_keyframe
< 35 ) {
2385 center_slide
.angle
+=16;
2388 cover_animation_keyframe
= 0;
2389 pf_state
= pf_show_tracks
;
2394 Animation step for zooming out the current cover
2396 void update_cover_out_animation(void)
2398 cover_animation_keyframe
++;
2399 if( cover_animation_keyframe
<= 15 ) {
2400 center_slide
.angle
-=16;
2402 else if( cover_animation_keyframe
< 35 ) {
2403 center_slide
.distance
+=5;
2404 center_slide
.angle
-=1;
2408 cover_animation_keyframe
= 0;
2414 Draw a blue gradient at y with height h
2416 static inline void draw_gradient(int y
, int h
)
2419 inc
= (100 << 8) / h
;
2421 selected_track_pulse
= (selected_track_pulse
+1) % 10;
2422 int c2
= selected_track_pulse
- 5;
2423 for (r
=0; r
<h
; r
++) {
2424 #ifdef HAVE_LCD_COLOR
2425 mylcd_set_foreground(G_PIX(c2
+80-(c
>> 9), c2
+100-(c
>> 9),
2428 mylcd_set_foreground(G_BRIGHT(c2
+160-(c
>> 8)));
2430 mylcd_hline(0, LCD_WIDTH
, r
+y
);
2439 static void track_list_yh(int char_height
)
2441 switch (show_album_name
)
2443 case ALBUM_NAME_HIDE
:
2444 track_list_y
= (show_fps
? char_height
: 0);
2445 track_list_h
= LCD_HEIGHT
- track_list_y
;
2447 case ALBUM_NAME_BOTTOM
:
2448 track_list_y
= (show_fps
? char_height
: 0);
2449 track_list_h
= LCD_HEIGHT
- track_list_y
- char_height
* 2;
2451 case ALBUM_NAME_TOP
:
2453 track_list_y
= char_height
* 2;
2454 track_list_h
= LCD_HEIGHT
- track_list_y
-
2455 (show_fps
? char_height
: 0);
2461 Reset the track list after a album change
2463 void reset_track_list(void)
2465 int char_height
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2467 track_list_yh(char_height
);
2468 track_list_visible_entries
= fmin( track_list_h
/char_height
, track_count
);
2469 start_index_track_list
= 0;
2471 last_selected_track
= -1;
2473 /* let the tracklist start more centered
2474 * if the screen isn't filled with tracks */
2475 total_height
= track_count
*char_height
;
2476 if (total_height
< track_list_h
)
2478 track_list_y
+= (track_list_h
- total_height
) / 2;
2479 track_list_h
= total_height
;
2484 Display the list of tracks
2486 void show_track_list(void)
2488 mylcd_clear_display();
2489 if ( center_slide
.slide_index
!= track_index
) {
2490 create_track_index(center_slide
.slide_index
);
2493 int titletxt_w
, titletxt_x
, color
, titletxt_h
;
2494 titletxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2496 int titletxt_y
= track_list_y
;
2498 track_i
= start_index_track_list
;
2499 for (;track_i
< track_list_visible_entries
+start_index_track_list
;
2502 char *trackname
= get_track_name(track_i
);
2503 if ( track_i
== selected_track
) {
2504 if (selected_track
!= last_selected_track
) {
2505 set_scroll_line(trackname
, PF_SCROLL_TRACK
);
2506 last_selected_track
= selected_track
;
2508 draw_gradient(titletxt_y
, titletxt_h
);
2509 titletxt_x
= get_scroll_line_offset(PF_SCROLL_TRACK
);
2513 titletxt_w
= mylcd_getstringsize(trackname
, NULL
, NULL
);
2514 titletxt_x
= (LCD_WIDTH
-titletxt_w
)/2;
2515 color
= 250 - (abs(selected_track
- track_i
) * 200 / track_count
);
2517 mylcd_set_foreground(G_BRIGHT(color
));
2518 mylcd_putsxy(titletxt_x
,titletxt_y
,trackname
);
2519 titletxt_y
+= titletxt_h
;
2523 void select_next_track(void)
2525 if ( selected_track
< track_count
- 1 ) {
2527 if (selected_track
==(track_list_visible_entries
+start_index_track_list
))
2528 start_index_track_list
++;
2532 void select_prev_track(void)
2534 if (selected_track
> 0 ) {
2535 if (selected_track
==start_index_track_list
) start_index_track_list
--;
2540 #if PF_PLAYBACK_CAPABLE
2542 * Puts the current tracklist into a newly created playlist and starts playling
2544 void start_playback(bool append
)
2546 static int old_playlist
= -1, old_shuffle
= 0;
2548 int position
= selected_track
;
2549 int shuffle
= rb
->global_settings
->playlist_shuffle
;
2550 /* reuse existing playlist if possible
2551 * regenerate if shuffle is on or changed, since playlist index and
2552 * selected track are "out of sync" */
2553 if (!shuffle
&& !append
&& center_slide
.slide_index
== old_playlist
2554 && (old_shuffle
== shuffle
))
2558 /* First, replace the current playlist with a new one */
2559 else if (append
|| (rb
->playlist_remove_all_tracks(NULL
) == 0
2560 && rb
->playlist_create(NULL
, NULL
) == 0))
2564 if (rb
->playlist_insert_track(NULL
, get_track_filename(count
),
2565 PLAYLIST_INSERT_LAST
, false, true) < 0)
2567 } while(++count
< track_count
);
2568 rb
->playlist_sync(NULL
);
2573 if (rb
->global_settings
->playlist_shuffle
)
2574 position
= rb
->playlist_shuffle(*rb
->current_tick
, selected_track
);
2576 /* TODO: can we adjust selected_track if !play_selected ?
2577 * if shuffle, we can't predict the playing track easily, and for either
2578 * case the track list doesn't get auto scrolled*/
2580 rb
->playlist_start(position
, 0);
2581 old_playlist
= center_slide
.slide_index
;
2582 old_shuffle
= shuffle
;
2587 Draw the current album name
2589 void draw_album_text(void)
2591 if (show_album_name
== ALBUM_NAME_HIDE
)
2596 int albumtxt_x
, albumtxt_y
;
2600 /* Draw album text */
2601 if ( pf_state
== pf_scrolling
) {
2602 c
= ((slide_frame
& 0xffff )/ 255);
2603 if (step
< 0) c
= 255-c
;
2604 if (c
> 128 ) { /* half way to next slide .. still not perfect! */
2605 albumtxt_index
= center_index
+step
;
2609 albumtxt_index
= center_index
;
2614 albumtxt_index
= center_index
;
2617 albumtxt
= get_album_name(albumtxt_index
);
2619 mylcd_set_foreground(G_BRIGHT(c
));
2620 if (albumtxt_index
!= prev_albumtxt_index
) {
2621 set_scroll_line(albumtxt
, PF_SCROLL_ALBUM
);
2622 prev_albumtxt_index
= albumtxt_index
;
2625 char_height
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2626 if (show_album_name
== ALBUM_NAME_TOP
)
2627 albumtxt_y
= char_height
/ 2;
2629 albumtxt_y
= LCD_HEIGHT
- char_height
- char_height
/2;
2631 albumtxt_x
= get_scroll_line_offset(PF_SCROLL_ALBUM
);
2632 mylcd_putsxy(albumtxt_x
, albumtxt_y
, albumtxt
);
2636 Display an error message and wait for input.
2638 void error_wait(const char *message
)
2640 rb
->splashf(0, "%s. Press any button to continue.", message
);
2641 while (rb
->get_action(CONTEXT_STD
, 1) == ACTION_NONE
)
2647 Main function that also contain the main plasma
2654 rb
->lcd_setfont(FONT_UI
);
2656 if ( ! rb
->dir_exists( CACHE_PREFIX
) ) {
2657 if ( rb
->mkdir( CACHE_PREFIX
) < 0 ) {
2658 error_wait("Could not create directory " CACHE_PREFIX
);
2659 return PLUGIN_ERROR
;
2663 configfile_load(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2665 draw_splashscreen();
2666 if(backlight_mode
== 0) {
2667 /* Turn off backlight timeout */
2668 backlight_ignore_timeout();
2671 init_scroll_lines();
2672 init_reflect_table();
2674 ALIGN_BUFFER(buf
, buf_size
, 4);
2675 ret
= create_album_index();
2676 if (ret
== ERROR_BUFFER_FULL
) {
2677 error_wait("Not enough memory for album names");
2678 return PLUGIN_ERROR
;
2679 } else if (ret
== ERROR_NO_ALBUMS
) {
2680 error_wait("No albums found. Please enable database");
2681 return PLUGIN_ERROR
;
2684 ALIGN_BUFFER(buf
, buf_size
, 4);
2685 number_of_slides
= album_count
;
2686 if ((cache_version
!= CACHE_VERSION
) && !create_albumart_cache()) {
2687 cache_version
= CACHE_REBUILD
;
2688 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2689 error_wait("Could not create album art cache");
2690 return PLUGIN_ERROR
;
2693 if (!create_empty_slide(cache_version
!= CACHE_VERSION
)) {
2694 cache_version
= CACHE_REBUILD
;
2695 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2696 error_wait("Could not load the empty slide");
2697 return PLUGIN_ERROR
;
2699 if (cache_version
!= CACHE_VERSION
)
2701 cache_version
= CACHE_VERSION
;
2702 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2705 buflib_init(&buf_ctx
, (void *)buf
, buf_size
);
2707 if (!(empty_slide_hid
= read_pfraw(EMPTY_SLIDE
, 0)))
2709 error_wait("Unable to load empty slide image");
2710 return PLUGIN_ERROR
;
2713 if (!create_pf_thread()) {
2714 error_wait("Cannot create thread!");
2715 return PLUGIN_ERROR
;
2721 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) {
2724 cache
[i
].next
= i
+ 1;
2725 cache
[i
].prev
= i
- 1;
2727 cache
[0].prev
= i
- 1;
2728 cache
[i
- 1].next
= 0;
2743 set_current_slide(get_wps_current_index());
2749 long last_update
= *rb
->current_tick
;
2750 long current_update
;
2751 long update_interval
= 100;
2755 bool instant_update
;
2758 grey_set_drawmode(DRMODE_FG
);
2760 rb
->lcd_set_drawmode(DRMODE_FG
);
2762 current_update
= *rb
->current_tick
;
2765 /* Initial rendering */
2766 instant_update
= false;
2768 update_scroll_lines();
2771 switch ( pf_state
) {
2773 update_scroll_animation();
2774 render_all_slides();
2775 instant_update
= true;
2778 update_cover_in_animation();
2779 render_all_slides();
2780 instant_update
= true;
2783 update_cover_out_animation();
2784 render_all_slides();
2785 instant_update
= true;
2787 case pf_show_tracks
:
2791 render_all_slides();
2796 if (current_update
- last_update
> update_interval
) {
2797 fps
= frames
* HZ
/ (current_update
- last_update
);
2798 last_update
= current_update
;
2805 mylcd_set_foreground(G_BRIGHT(255));
2807 mylcd_set_foreground(G_PIX(255,0,0));
2809 rb
->snprintf(fpstxt
, sizeof(fpstxt
), "FPS: %d", fps
);
2810 if (show_album_name
== ALBUM_NAME_TOP
)
2811 fpstxt_y
= LCD_HEIGHT
-
2812 rb
->screens
[SCREEN_MAIN
]->getcharheight();
2815 mylcd_putsxy(0, fpstxt_y
, fpstxt
);
2820 /* Copy offscreen buffer to LCD and give time to other threads */
2824 /*/ Handle buttons */
2825 button
= rb
->get_custom_action(CONTEXT_PLUGIN
2826 #ifndef USE_CORE_PREVNEXT
2827 |(pf_state
== pf_show_tracks
? 1 : 0)
2829 ,instant_update
? 0 : HZ
/16,
2836 return PLUGIN_GOTO_WPS
;
2838 if ( pf_state
== pf_show_tracks
)
2840 buflib_buffer_in(&buf_ctx
, borrowed
);
2843 pf_state
= pf_cover_out
;
2845 if (pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2853 if ( ret
== -2 ) return PLUGIN_GOTO_WPS
;
2854 if ( ret
== -1 ) return PLUGIN_OK
;
2855 if ( ret
!= 0 ) return ret
;
2859 mylcd_set_drawmode(DRMODE_FG
);
2863 case PF_NEXT_REPEAT
:
2864 if ( pf_state
== pf_show_tracks
)
2865 select_next_track();
2866 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2871 case PF_PREV_REPEAT
:
2872 if ( pf_state
== pf_show_tracks
)
2873 select_prev_track();
2874 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2875 show_previous_slide();
2877 #if PF_PLAYBACK_CAPABLE
2879 if ( auto_wps
!= 0 ) {
2880 if( pf_state
== pf_idle
) {
2881 create_track_index(center_slide
.slide_index
);
2883 start_playback(true);
2884 rb
->splash(HZ
*2, "Added to playlist");
2886 else if( pf_state
== pf_show_tracks
) {
2887 rb
->playlist_insert_track(NULL
, get_track_filename(selected_track
),
2888 PLAYLIST_INSERT_LAST
, false, true);
2889 rb
->playlist_sync(NULL
);
2890 rb
->splash(HZ
*2, "Added to playlist");
2896 if ( auto_wps
== 1 && pf_state
== pf_idle
) {
2897 pf_state
= pf_cover_in
;
2901 if ( pf_state
== pf_idle
) {
2902 #if PF_PLAYBACK_CAPABLE
2904 create_track_index(center_slide
.slide_index
);
2906 start_playback(false);
2907 last_album
= center_index
;
2908 return PLUGIN_GOTO_WPS
;
2912 pf_state
= pf_cover_in
;
2914 else if ( pf_state
== pf_show_tracks
) {
2915 #if PF_PLAYBACK_CAPABLE
2916 start_playback(false);
2918 last_album
= center_index
;
2919 return PLUGIN_GOTO_WPS
;
2925 exit_on_usb(button
);
2931 /*************************** Plugin entry point ****************************/
2933 enum plugin_status
plugin_start(const void *parameter
)
2939 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2940 rb
->cpu_boost(true);
2942 #if PF_PLAYBACK_CAPABLE
2943 buf
= rb
->plugin_get_buffer(&buf_size
);
2945 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
2947 if ((uintptr_t)buf
< (uintptr_t)plugin_start_addr
)
2949 uint32_t tmp_size
= (uintptr_t)plugin_start_addr
- (uintptr_t)buf
;
2950 buf_size
= MIN(buf_size
, tmp_size
);
2957 if (!grey_init(buf
, buf_size
, GREY_BUFFERED
|GREY_ON_COP
,
2958 LCD_WIDTH
, LCD_HEIGHT
, &grey_buf_used
))
2960 error_wait("Greylib init failed!");
2961 return PLUGIN_ERROR
;
2963 grey_setfont(FONT_UI
);
2964 buf_size
-= grey_buf_used
;
2965 buf
= (void*)(grey_buf_used
+ (char*)buf
);
2969 if ( ret
== PLUGIN_OK
|| ret
== PLUGIN_GOTO_WPS
) {
2970 if (configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
,
2973 rb
->splash(HZ
, "Error writing config.");