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"
39 /******************************* Globals ***********************************/
42 * Targets which use plugin_get_audio_buffer() can't have playback from
43 * within pictureflow itself, as the whole core audio buffer is occupied */
44 #define PF_PLAYBACK_CAPABLE (PLUGIN_BUFFER_SIZE > 0x10000)
46 #if PF_PLAYBACK_CAPABLE
47 #include "lib/playback_control.h"
50 #define PF_PREV ACTION_STD_PREV
51 #define PF_PREV_REPEAT ACTION_STD_PREVREPEAT
52 #define PF_NEXT ACTION_STD_NEXT
53 #define PF_NEXT_REPEAT ACTION_STD_NEXTREPEAT
54 #define PF_SELECT ACTION_STD_OK
55 #define PF_CONTEXT ACTION_STD_CONTEXT
56 #define PF_BACK ACTION_STD_CANCEL
57 #define PF_MENU ACTION_STD_MENU
58 #define PF_WPS ACTION_TREE_WPS
60 #define PF_QUIT (LAST_ACTION_PLACEHOLDER + 1)
61 #define PF_TRACKLIST (LAST_ACTION_PLACEHOLDER + 2)
63 #if defined(HAVE_SCROLLWHEEL) || CONFIG_KEYPAD == IRIVER_H10_PAD || \
64 CONFIG_KEYPAD == SAMSUNG_YH_PAD
65 #define USE_CORE_PREVNEXT
68 #ifndef USE_CORE_PREVNEXT
69 /* scrollwheel targets use the wheel, just as they do in lists,
70 * so there's no need for a special context,
71 * others use left/right here too (as oppsed to up/down in lists) */
72 const struct button_mapping pf_context_album_scroll
[] =
74 #ifdef HAVE_TOUCHSCREEN
75 {PF_PREV
, BUTTON_MIDLEFT
, BUTTON_NONE
},
76 {PF_PREV_REPEAT
, BUTTON_MIDLEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
77 {PF_NEXT
, BUTTON_MIDRIGHT
, BUTTON_NONE
},
78 {PF_NEXT_REPEAT
, BUTTON_MIDRIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
80 #if (CONFIG_KEYPAD == IAUDIO_M3_PAD || CONFIG_KEYPAD == MROBE500_PAD)
81 {PF_PREV
, BUTTON_RC_REW
, BUTTON_NONE
},
82 {PF_PREV_REPEAT
, BUTTON_RC_REW
|BUTTON_REPEAT
,BUTTON_NONE
},
83 {PF_NEXT
, BUTTON_RC_FF
, BUTTON_NONE
},
84 {PF_NEXT_REPEAT
, BUTTON_RC_FF
|BUTTON_REPEAT
, BUTTON_NONE
},
86 {PF_PREV
, BUTTON_LEFT
, BUTTON_NONE
},
87 {PF_PREV_REPEAT
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_NONE
},
88 {PF_NEXT
, BUTTON_RIGHT
, BUTTON_NONE
},
89 {PF_NEXT_REPEAT
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_NONE
},
90 {ACTION_NONE
, BUTTON_LEFT
|BUTTON_REL
, BUTTON_LEFT
},
91 {ACTION_NONE
, BUTTON_RIGHT
|BUTTON_REL
, BUTTON_RIGHT
},
92 {ACTION_NONE
, BUTTON_LEFT
|BUTTON_REPEAT
, BUTTON_LEFT
},
93 {ACTION_NONE
, BUTTON_RIGHT
|BUTTON_REPEAT
, BUTTON_RIGHT
},
95 #if CONFIG_KEYPAD == ONDIO_PAD
96 {PF_SELECT
, BUTTON_UP
|BUTTON_REL
, BUTTON_UP
},
97 {PF_CONTEXT
, BUTTON_UP
|BUTTON_REPEAT
, BUTTON_UP
},
98 {ACTION_NONE
, BUTTON_UP
, BUTTON_NONE
},
99 {ACTION_NONE
, BUTTON_DOWN
, BUTTON_NONE
},
100 {ACTION_NONE
, BUTTON_DOWN
|BUTTON_REPEAT
, BUTTON_NONE
},
102 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_PLUGIN
|1)
104 #endif /* !USE_CORE_PREVNEXT */
106 const struct button_mapping pf_context_buttons
[] =
108 #ifdef HAVE_TOUCHSCREEN
109 {PF_SELECT
, BUTTON_CENTER
, BUTTON_NONE
},
110 {PF_BACK
, BUTTON_BOTTOMRIGHT
, BUTTON_NONE
},
112 #if CONFIG_KEYPAD == ARCHOS_AV300_PAD
113 {PF_QUIT
, BUTTON_OFF
, BUTTON_NONE
},
114 #elif CONFIG_KEYPAD == SANSA_C100_PAD
115 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
116 #elif CONFIG_KEYPAD == CREATIVEZV_PAD || CONFIG_KEYPAD == CREATIVEZVM_PAD || \
117 CONFIG_KEYPAD == PHILIPS_HDD1630_PAD || CONFIG_KEYPAD == IAUDIO67_PAD || \
118 CONFIG_KEYPAD == GIGABEAT_PAD || CONFIG_KEYPAD == GIGABEAT_S_PAD || \
119 CONFIG_KEYPAD == MROBE100_PAD || CONFIG_KEYPAD == MROBE500_PAD || \
120 CONFIG_KEYPAD == PHILIPS_SA9200_PAD || CONFIG_KEYPAD == SANSA_CLIP_PAD
121 {PF_QUIT
, BUTTON_POWER
, BUTTON_NONE
},
122 #elif CONFIG_KEYPAD == SANSA_FUZE_PAD
123 {PF_QUIT
, BUTTON_HOME
|BUTTON_REPEAT
, BUTTON_NONE
},
124 {PF_TRACKLIST
, BUTTON_RIGHT
, BUTTON_NONE
},
125 /* These all use short press of BUTTON_POWER for menu, map long POWER to quit
127 #elif CONFIG_KEYPAD == SANSA_C200_PAD || CONFIG_KEYPAD == SANSA_M200_PAD || \
128 CONFIG_KEYPAD == IRIVER_H10_PAD || CONFIG_KEYPAD == COWON_D2_PAD
129 {PF_QUIT
, BUTTON_POWER
|BUTTON_REPEAT
, BUTTON_POWER
},
130 #if CONFIG_KEYPAD == COWON_D2_PAD
131 {PF_BACK
, BUTTON_POWER
|BUTTON_REL
, BUTTON_POWER
},
132 {ACTION_NONE
, BUTTON_POWER
, BUTTON_NONE
},
134 #elif CONFIG_KEYPAD == SANSA_E200_PAD
135 {PF_QUIT
, BUTTON_POWER
, BUTTON_NONE
},
136 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
137 {PF_QUIT
, BUTTON_EQ
, BUTTON_NONE
},
138 #elif (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
139 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
140 || (CONFIG_KEYPAD == IPOD_4G_PAD)
141 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
142 #elif CONFIG_KEYPAD == LOGIK_DAX_PAD
143 {PF_QUIT
, BUTTON_POWERPLAY
|BUTTON_REPEAT
, BUTTON_POWERPLAY
},
144 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
145 {PF_QUIT
, BUTTON_RC_REC
, BUTTON_NONE
},
146 #elif CONFIG_KEYPAD == MEIZU_M6SL_PAD
147 {PF_QUIT
, BUTTON_MENU
|BUTTON_REPEAT
, BUTTON_MENU
},
148 #elif CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD || \
149 CONFIG_KEYPAD == RECORDER_PAD || CONFIG_KEYPAD == ONDIO_PAD
150 {PF_QUIT
, BUTTON_OFF
, BUTTON_NONE
},
151 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
152 {PF_QUIT
, BUTTON_REC
, BUTTON_NONE
},
154 #if CONFIG_KEYPAD == IAUDIO_M3_PAD
155 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD
|CONTEXT_REMOTE
)
157 LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE
)
160 const struct button_mapping
*pf_contexts
[] =
162 #ifndef USE_CORE_PREVNEXT
163 pf_context_album_scroll
,
170 #define N_BRIGHT(y) LCD_BRIGHTNESS(y)
171 #else /* LCD_DEPTH <= 1 */
172 #define N_BRIGHT(y) ((y > 127) ? 0 : 1)
173 #ifdef HAVE_NEGATIVE_LCD /* m:robe 100, Clip */
174 #define PICTUREFLOW_DRMODE DRMODE_SOLID
176 #define PICTUREFLOW_DRMODE (DRMODE_SOLID|DRMODE_INVERSEVID)
178 #endif /* LCD_DEPTH <= 1 */
181 #define LCD_BUF _grey_info.buffer
182 #define G_PIX(r,g,b) \
183 (77 * (unsigned)(r) + 150 * (unsigned)(g) + 29 * (unsigned)(b)) / 256
184 #define N_PIX(r,g,b) N_BRIGHT(G_PIX(r,g,b))
185 #define G_BRIGHT(y) (y)
186 #define BUFFER_WIDTH _grey_info.width
187 #define BUFFER_HEIGHT _grey_info.height
188 typedef unsigned char pix_t
;
189 #else /* LCD_DEPTH >= 8 */
190 #define LCD_BUF rb->lcd_framebuffer
191 #define G_PIX LCD_RGBPACK
192 #define N_PIX LCD_RGBPACK
193 #define G_BRIGHT(y) LCD_RGBPACK(y,y,y)
194 #define N_BRIGHT(y) LCD_RGBPACK(y,y,y)
195 #define BUFFER_WIDTH LCD_WIDTH
196 #define BUFFER_HEIGHT LCD_HEIGHT
197 typedef fb_data pix_t
;
198 #endif /* LCD_DEPTH >= 8 */
200 /* for fixed-point arithmetic, we need minimum 32-bit long
201 long long (64-bit) might be useful for multiplication and division */
203 #define PFREAL_SHIFT 10
204 #define PFREAL_FACTOR (1 << PFREAL_SHIFT)
205 #define PFREAL_ONE (1 << PFREAL_SHIFT)
206 #define PFREAL_HALF (PFREAL_ONE >> 1)
209 #define IANGLE_MAX 1024
210 #define IANGLE_MASK 1023
212 #define REFLECT_TOP (LCD_HEIGHT * 2 / 3)
213 #define REFLECT_HEIGHT (LCD_HEIGHT - REFLECT_TOP)
214 #define DISPLAY_HEIGHT REFLECT_TOP
215 #define DISPLAY_WIDTH MAX((LCD_HEIGHT * LCD_PIXEL_ASPECT_HEIGHT / \
216 LCD_PIXEL_ASPECT_WIDTH / 2), (LCD_WIDTH * 2 / 5))
217 #define REFLECT_SC ((0x10000U * 3 + (REFLECT_HEIGHT * 5 - 1)) / \
218 (REFLECT_HEIGHT * 5))
219 #define DISPLAY_OFFS ((LCD_HEIGHT / 2) - REFLECT_HEIGHT)
220 #define CAM_DIST MAX(MIN(LCD_HEIGHT,LCD_WIDTH),120)
221 #define CAM_DIST_R (CAM_DIST << PFREAL_SHIFT)
222 #define DISPLAY_LEFT_R (PFREAL_HALF - LCD_WIDTH * PFREAL_HALF)
223 #define MAXSLIDE_LEFT_R (PFREAL_HALF - DISPLAY_WIDTH * PFREAL_HALF)
225 #define SLIDE_CACHE_SIZE 64 /* probably more than can be loaded */
227 #define MAX_SLIDES_COUNT 10
229 #define THREAD_STACK_SIZE DEFAULT_STACK_SIZE + 0x200
230 #define CACHE_PREFIX PLUGIN_DEMOS_DATA_DIR "/pictureflow"
233 #define EV_WAKEUP 1337
235 #define EMPTY_SLIDE CACHE_PREFIX "/emptyslide.pfraw"
236 #define EMPTY_SLIDE_BMP PLUGIN_DEMOS_DIR "/pictureflow_emptyslide.bmp"
237 #define SPLASH_BMP PLUGIN_DEMOS_DIR "/pictureflow_splash.bmp"
239 /* some magic numbers for cache_version. */
240 #define CACHE_REBUILD 0
241 #define CACHE_UPDATE 1
243 /* Error return values */
244 #define ERROR_NO_ALBUMS -1
245 #define ERROR_BUFFER_FULL -2
247 /* current version for cover cache */
248 #define CACHE_VERSION 3
249 #define CONFIG_VERSION 1
250 #define CONFIG_FILE "pictureflow.cfg"
252 /** structs we use */
263 int index
; /* index of the cached slide */
264 int hid
; /* handle ID of the cached slide */
265 short next
; /* "next" slide, with LRU last */
266 short prev
; /* "previous" slide */
276 int name_idx
; /* offset to the track name */
278 #if PF_PLAYBACK_CAPABLE
279 /* offset to the filename in the string, needed for playlist generation */
291 struct load_slide_event_data
{
296 enum pf_scroll_line_type
{
302 struct pf_scroll_line_info
{
303 long ticks
; /* number of ticks between each move */
304 long delay
; /* number of ticks to delay starting scrolling */
305 int step
; /* pixels to move */
306 long next_scroll
; /* tick of the next move */
309 struct pf_scroll_line
{
310 int width
; /* width of the string */
311 int offset
; /* x coordinate of the string */
312 int step
; /* 0 if scroll is disabled. otherwise, pixels to move */
313 long start_tick
; /* tick when to start scrolling */
316 struct pfraw_header
{
317 int32_t width
; /* bmap width in pixels */
318 int32_t height
; /* bmap height in pixels */
321 enum show_album_name_values
{
326 static char* show_album_name_conf
[] =
333 #define MAX_SPACING 40
334 #define MAX_MARGIN 80
336 /* config values and their defaults */
337 static int slide_spacing
= DISPLAY_WIDTH
/ 4;
338 static int center_margin
= (LCD_WIDTH
- DISPLAY_WIDTH
) / 12;
339 static int num_slides
= 4;
340 static int zoom
= 100;
341 static bool show_fps
= false;
342 static int auto_wps
= 0;
343 static int last_album
= 0;
344 static int backlight_mode
= 0;
345 static bool resize
= true;
346 static int cache_version
= 0;
347 static int show_album_name
= (LCD_HEIGHT
> 100)
348 ? ALBUM_NAME_TOP
: ALBUM_NAME_BOTTOM
;
350 static struct configdata config
[] =
352 { TYPE_INT
, 0, MAX_SPACING
, { .int_p
= &slide_spacing
}, "slide spacing",
354 { TYPE_INT
, 0, MAX_MARGIN
, { .int_p
= ¢er_margin
}, "center margin",
356 { TYPE_INT
, 0, MAX_SLIDES_COUNT
, { .int_p
= &num_slides
}, "slides count",
358 { TYPE_INT
, 0, 300, { .int_p
= &zoom
}, "zoom", NULL
},
359 { TYPE_BOOL
, 0, 1, { .bool_p
= &show_fps
}, "show fps", NULL
},
360 { TYPE_BOOL
, 0, 1, { .bool_p
= &resize
}, "resize", NULL
},
361 { TYPE_INT
, 0, 100, { .int_p
= &cache_version
}, "cache version", NULL
},
362 { TYPE_ENUM
, 0, 3, { .int_p
= &show_album_name
}, "show album name",
363 show_album_name_conf
},
364 { TYPE_INT
, 0, 2, { .int_p
= &auto_wps
}, "auto wps", NULL
},
365 { TYPE_INT
, 0, 999999, { .int_p
= &last_album
}, "last album", NULL
},
366 { TYPE_INT
, 0, 1, { .int_p
= &backlight_mode
}, "backlight", NULL
}
369 #define CONFIG_NUM_ITEMS (sizeof(config) / sizeof(struct configdata))
371 /** below we allocate the memory we want to use **/
373 static pix_t
*buffer
; /* for now it always points to the lcd framebuffer */
374 static uint8_t reflect_table
[REFLECT_HEIGHT
];
375 static struct slide_data center_slide
;
376 static struct slide_data left_slides
[MAX_SLIDES_COUNT
];
377 static struct slide_data right_slides
[MAX_SLIDES_COUNT
];
378 static int slide_frame
;
382 static int center_index
= 0; /* index of the slide that is in the center */
384 static PFreal offsetX
;
385 static PFreal offsetY
;
386 static int number_of_slides
;
388 static struct slide_cache cache
[SLIDE_CACHE_SIZE
];
389 static int cache_free
;
390 static int cache_used
= -1;
391 static int cache_left_index
= -1;
392 static int cache_right_index
= -1;
393 static int cache_center_index
= -1;
395 /* use long for aligning */
396 unsigned long thread_stack
[THREAD_STACK_SIZE
/ sizeof(long)];
397 /* queue (as array) for scheduling load_surface */
399 static int empty_slide_hid
;
401 unsigned int thread_id
;
402 struct event_queue thread_q
;
404 static struct tagcache_search tcs
;
406 static struct buflib_context buf_ctx
;
408 static struct album_data
*album
;
409 static char *album_names
;
410 static int album_count
;
412 static struct track_data
*tracks
;
413 static char *track_names
;
414 static size_t borrowed
= 0;
415 static int track_count
;
416 static int track_index
;
417 static int selected_track
;
418 static int selected_track_pulse
;
419 void reset_track_list(void);
424 static bool thread_is_running
;
426 static int cover_animation_keyframe
;
427 static int extra_fade
;
429 static struct pf_scroll_line_info scroll_line_info
;
430 static struct pf_scroll_line scroll_lines
[PF_MAX_SCROLL_LINES
];
431 static int prev_albumtxt_index
= -1;
432 static int last_selected_track
= -1;
434 static int start_index_track_list
= 0;
435 static int track_list_visible_entries
= 0;
436 static int track_list_y
;
437 static int track_list_h
;
440 Proposals for transitions:
442 pf_idle -> pf_scrolling : NEXT_ALBUM/PREV_ALBUM pressed
443 -> pf_cover_in -> pf_show_tracks : SELECT_ALBUM clicked
445 pf_scrolling -> pf_idle : NEXT_ALBUM/PREV_ALBUM released
447 pf_show_tracks -> pf_cover_out -> pf_idle : SELECT_ALBUM pressed
450 pf_show_tracks -> pf_cover_out -> pf_idle : MENU_PRESSED pressed
451 pf_show_tracks -> play_track() -> exit() : SELECT_ALBUM pressed
453 pf_idle, pf_scrolling -> show_menu(): MENU_PRESSED
466 static bool free_slide_prio(int prio
);
467 static inline unsigned fade_color(pix_t c
, unsigned a
);
468 bool load_new_slide(void);
469 int load_surface(int);
471 static inline PFreal
fmul(PFreal a
, PFreal b
)
473 return (a
*b
) >> PFREAL_SHIFT
;
477 * This version preshifts each operand, which is useful when we know how many
478 * of the least significant bits will be empty, or are worried about overflow
479 * in a particular calculation
481 static inline PFreal
fmuln(PFreal a
, PFreal b
, int ps1
, int ps2
)
483 return ((a
>> ps1
) * (b
>> ps2
)) >> (PFREAL_SHIFT
- ps1
- ps2
);
486 /* ARMv5+ has a clz instruction equivalent to our function.
488 #if (defined(CPU_ARM) && (ARM_ARCH > 4))
489 static inline int clz(uint32_t v
)
491 return __builtin_clz(v
);
494 /* Otherwise, use our clz, which can be inlined */
495 #elif defined(CPU_COLDFIRE)
496 /* This clz is based on the log2(n) implementation at
497 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
498 * A clz benchmark plugin showed this to be about 14% faster on coldfire
499 * than the LUT-based version.
501 static inline int clz(uint32_t v
)
533 static const char clz_lut
[16] = { 4, 3, 2, 2, 1, 1, 1, 1,
534 0, 0, 0, 0, 0, 0, 0, 0 };
535 /* This clz is based on the log2(n) implementation at
536 * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup
537 * It is not any faster than the one above, but trades 16B in the lookup table
538 * for a savings of 12B per each inlined call.
540 static inline int clz(uint32_t v
)
558 return r
+ clz_lut
[v
];
562 /* Return the maximum possible left shift for a signed int32, without
565 static inline int allowed_shift(int32_t val
)
567 uint32_t uval
= val
^ (val
>> 31);
568 return clz(uval
) - 1;
571 /* Calculate num/den, with the result shifted left by PFREAL_SHIFT, by shifting
572 * num and den before dividing.
574 static inline PFreal
fdiv(PFreal num
, PFreal den
)
576 int shift
= allowed_shift(num
);
577 shift
= MIN(PFREAL_SHIFT
, shift
);
579 den
>>= PFREAL_SHIFT
- shift
;
583 #define fmin(a,b) (((a) < (b)) ? (a) : (b))
584 #define fmax(a,b) (((a) > (b)) ? (a) : (b))
585 #define fabs(a) (a < 0 ? -a : a)
586 #define fbound(min,val,max) (fmax((min),fmin((max),(val))))
588 #if CONFIG_CPU == SH7034
589 /* 16*16->32 bit multiplication is a single instrcution on the SH1 */
590 #define MULUQ(a, b) ((uint32_t) (((uint16_t) (a)) * ((uint16_t) (b))))
592 #define MULUQ(a, b) ((a) * (b))
597 #define fmul(a,b) ( ((a)*(b)) >> PFREAL_SHIFT )
598 #define fdiv(n,m) ( ((n)<< PFREAL_SHIFT ) / m )
600 #define fconv(a, q1, q2) (((q2)>(q1)) ? (a)<<((q2)-(q1)) : (a)>>((q1)-(q2)))
601 #define tofloat(a, q) ( (float)(a) / (float)(1<<(q)) )
603 static inline PFreal
fmul(PFreal a
, PFreal b
)
605 return (a
*b
) >> PFREAL_SHIFT
;
608 static inline PFreal
fdiv(PFreal n
, PFreal m
)
610 return (n
<<(PFREAL_SHIFT
))/m
;
614 /* warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! */
615 static const short sin_tab
[] = {
616 0, 100, 200, 297, 392, 483, 569, 650,
617 724, 792, 851, 903, 946, 980, 1004, 1019,
618 1024, 1019, 1004, 980, 946, 903, 851, 792,
619 724, 650, 569, 483, 392, 297, 200, 100,
620 0, -100, -200, -297, -392, -483, -569, -650,
621 -724, -792, -851, -903, -946, -980, -1004, -1019,
622 -1024, -1019, -1004, -980, -946, -903, -851, -792,
623 -724, -650, -569, -483, -392, -297, -200, -100,
627 static inline PFreal
fsin(int iangle
)
629 iangle
&= IANGLE_MASK
;
631 int i
= (iangle
>> 4);
632 PFreal p
= sin_tab
[i
];
633 PFreal q
= sin_tab
[(i
+1)];
635 return p
+ g
* (iangle
-i
*16)/16;
638 static inline PFreal
fcos(int iangle
)
640 return fsin(iangle
+ (IANGLE_MAX
>> 2));
643 static inline unsigned scale_val(unsigned val
, unsigned bits
)
645 val
= val
* ((1 << bits
) - 1);
646 return ((val
>> 8) + val
+ 128) >> 8;
649 static void output_row_8_transposed(uint32_t row
, void * row_in
,
650 struct scaler_context
*ctx
)
652 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
653 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
655 uint8_t *qp
= (uint8_t*)row_in
;
656 for (; dest
< end
; dest
+= ctx
->bm
->height
)
659 struct uint8_rgb
*qp
= (struct uint8_rgb
*)row_in
;
661 for (; dest
< end
; dest
+= ctx
->bm
->height
)
663 r
= scale_val(qp
->red
, 5);
664 g
= scale_val(qp
->green
, 6);
665 b
= scale_val((qp
++)->blue
, 5);
666 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
671 /* read_image_file() is called without FORMAT_TRANSPARENT so
672 * it's safe to ignore alpha channel in the next two functions */
673 static void output_row_32_transposed(uint32_t row
, void * row_in
,
674 struct scaler_context
*ctx
)
676 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
677 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
679 uint32_t *qp
= (uint32_t*)row_in
;
680 for (; dest
< end
; dest
+= ctx
->bm
->height
)
681 *dest
= SC_OUT(*qp
++, ctx
);
683 struct uint32_argb
*qp
= (struct uint32_argb
*)row_in
;
685 for (; dest
< end
; dest
+= ctx
->bm
->height
)
687 r
= scale_val(SC_OUT(qp
->r
, ctx
), 5);
688 g
= scale_val(SC_OUT(qp
->g
, ctx
), 6);
689 b
= scale_val(SC_OUT(qp
->b
, ctx
), 5);
691 *dest
= LCD_RGBPACK_LCD(r
,g
,b
);
696 #ifdef HAVE_LCD_COLOR
697 static void output_row_32_transposed_fromyuv(uint32_t row
, void * row_in
,
698 struct scaler_context
*ctx
)
700 pix_t
*dest
= (pix_t
*)ctx
->bm
->data
+ row
;
701 pix_t
*end
= dest
+ ctx
->bm
->height
* ctx
->bm
->width
;
702 struct uint32_argb
*qp
= (struct uint32_argb
*)row_in
;
703 for (; dest
< end
; dest
+= ctx
->bm
->height
)
705 unsigned r
, g
, b
, y
, u
, v
;
706 y
= SC_OUT(qp
->b
, ctx
);
707 u
= SC_OUT(qp
->g
, ctx
);
708 v
= SC_OUT(qp
->r
, ctx
);
710 yuv_to_rgb(y
, u
, v
, &r
, &g
, &b
);
714 *dest
= LCD_RGBPACK_LCD(r
, g
, b
);
719 static unsigned int get_size(struct bitmap
*bm
)
721 return bm
->width
* bm
->height
* sizeof(pix_t
);
724 const struct custom_format format_transposed
= {
725 .output_row_8
= output_row_8_transposed
,
726 #ifdef HAVE_LCD_COLOR
728 output_row_32_transposed
,
729 output_row_32_transposed_fromyuv
732 .output_row_32
= output_row_32_transposed
,
737 static const struct button_mapping
* get_context_map(int context
)
739 return pf_contexts
[context
& ~CONTEXT_PLUGIN
];
743 static void init_scroll_lines(void)
746 static const char scroll_tick_table
[16] = {
748 1, 1.25, 1.55, 2, 2.5, 3.12, 4, 5, 6.25, 8.33, 10, 12.5, 16.7, 20, 25, 33 */
749 100, 80, 64, 50, 40, 32, 25, 20, 16, 12, 10, 8, 6, 5, 4, 3
752 scroll_line_info
.ticks
= scroll_tick_table
[rb
->global_settings
->scroll_speed
];
753 scroll_line_info
.step
= rb
->global_settings
->scroll_step
;
754 scroll_line_info
.delay
= rb
->global_settings
->scroll_delay
/ (HZ
/ 10);
755 scroll_line_info
.next_scroll
= *rb
->current_tick
;
756 for (i
= 0; i
< PF_MAX_SCROLL_LINES
; i
++)
757 scroll_lines
[i
].step
= 0;
760 static void set_scroll_line(const char *str
, enum pf_scroll_line_type type
)
762 struct pf_scroll_line
*s
= &scroll_lines
[type
];
763 s
->width
= mylcd_getstringsize(str
, NULL
, NULL
);
766 s
->start_tick
= *rb
->current_tick
+ scroll_line_info
.delay
;
767 if (LCD_WIDTH
- s
->width
< 0)
768 s
->step
= scroll_line_info
.step
;
770 s
->offset
= (LCD_WIDTH
- s
->width
) / 2;
773 static int get_scroll_line_offset(enum pf_scroll_line_type type
)
775 return scroll_lines
[type
].offset
;
778 static void update_scroll_lines(void)
782 if (TIME_BEFORE(*rb
->current_tick
, scroll_line_info
.next_scroll
))
785 scroll_line_info
.next_scroll
= *rb
->current_tick
+ scroll_line_info
.ticks
;
787 for (i
= 0; i
< PF_MAX_SCROLL_LINES
; i
++)
789 struct pf_scroll_line
*s
= &scroll_lines
[i
];
790 if (s
->step
&& TIME_BEFORE(s
->start_tick
, *rb
->current_tick
))
792 s
->offset
-= s
->step
;
794 if (s
->offset
>= 0) {
795 /* at beginning of line */
797 s
->step
= scroll_line_info
.step
;
798 s
->start_tick
= *rb
->current_tick
+ scroll_line_info
.delay
* 2;
800 if (s
->offset
<= LCD_WIDTH
- s
->width
) {
802 s
->offset
= LCD_WIDTH
- s
->width
;
803 s
->step
= -scroll_line_info
.step
;
804 s
->start_tick
= *rb
->current_tick
+ scroll_line_info
.delay
* 2;
810 /* Create the lookup table with the scaling values for the reflections */
811 void init_reflect_table(void)
814 for (i
= 0; i
< REFLECT_HEIGHT
; i
++)
816 (768 * (REFLECT_HEIGHT
- i
) + (5 * REFLECT_HEIGHT
/ 2)) /
817 (5 * REFLECT_HEIGHT
);
821 Create an index of all albums from the database.
822 Also store the album names so we can access them later.
824 int create_album_index(void)
826 album
= ((struct album_data
*)(buf_size
+ (char *) buf
)) - 1;
827 rb
->memset(&tcs
, 0, sizeof(struct tagcache_search
) );
829 rb
->tagcache_search(&tcs
, tag_album
);
830 unsigned int l
, name_idx
= 0;
832 while (rb
->tagcache_get_next(&tcs
))
834 buf_size
-= sizeof(struct album_data
);
836 album
[-album_count
].name_idx
= name_idx
;
839 /* not enough memory */
840 return ERROR_BUFFER_FULL
;
842 rb
->strcpy(buf
, tcs
.result
);
844 buf
= l
+ (char *)buf
;
845 album
[-album_count
].seek
= tcs
.result_seek
;
849 rb
->tagcache_search_finish(&tcs
);
850 ALIGN_BUFFER(buf
, buf_size
, 4);
852 struct album_data
* tmp_album
= (struct album_data
*)buf
;
853 for (i
= album_count
- 1; i
>= 0; i
--)
854 tmp_album
[i
] = album
[-i
];
856 buf
= album
+ album_count
;
857 return (album_count
> 0) ? 0 : ERROR_NO_ALBUMS
;
861 Return a pointer to the album name of the given slide_index
863 char* get_album_name(const int slide_index
)
865 return album_names
+ album
[slide_index
].name_idx
;
869 Return a pointer to the track name of the active album
870 create_track_index has to be called first.
872 char* get_track_name(const int track_index
)
874 if ( track_index
< track_count
)
875 return track_names
+ tracks
[track_index
].name_idx
;
878 #if PF_PLAYBACK_CAPABLE
879 char* get_track_filename(const int track_index
)
881 if ( track_index
< track_count
)
882 return track_names
+ tracks
[track_index
].filename_idx
;
887 int get_wps_current_index(void)
889 struct mp3entry
*id3
= rb
->audio_current_track();
890 if(id3
&& id3
->album
) {
892 for( i
=0; i
< album_count
; i
++ )
894 if(!rb
->strcmp(album_names
+ album
[i
].name_idx
, id3
->album
))
902 Compare two unsigned ints passed via pointers.
904 int compare_tracks (const void *a_v
, const void *b_v
)
906 uint32_t a
= ((struct track_data
*)a_v
)->sort
;
907 uint32_t b
= ((struct track_data
*)b_v
)->sort
;
912 Create the track index of the given slide_index.
914 void create_track_index(const int slide_index
)
916 if ( slide_index
== track_index
)
918 track_index
= slide_index
;
920 if (!rb
->tagcache_search(&tcs
, tag_title
))
923 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
925 int string_index
= 0, track_num
;
928 track_names
= rb
->buflib_buffer_out(&buf_ctx
, &out
);
930 int avail
= borrowed
;
931 tracks
= (struct track_data
*)(track_names
+ borrowed
);
932 while (rb
->tagcache_get_next(&tcs
))
934 int len
= 0, fn_idx
= 0;
936 avail
-= sizeof(struct track_data
);
937 track_num
= rb
->tagcache_get_numeric(&tcs
, tag_tracknumber
);
938 disc_num
= rb
->tagcache_get_numeric(&tcs
, tag_discnumber
);
946 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
947 "%d.%02d: %s", disc_num
, track_num
, tcs
.result
);
949 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
950 "%d: %s", track_num
, tcs
.result
);
955 fn_idx
= 1 + rb
->snprintf(track_names
+ string_index
, avail
,
960 #if PF_PLAYBACK_CAPABLE
961 int remain
= avail
- fn_idx
;
962 if (remain
>= MAX_PATH
)
963 { /* retrieve filename for building the playlist */
964 rb
->tagcache_retrieve(&tcs
, tcs
.idx_id
, tag_filename
,
965 track_names
+ string_index
+ fn_idx
, remain
);
966 len
= fn_idx
+ rb
->strlen(track_names
+ string_index
+ fn_idx
) + 1;
967 /* make sure track name and file name are really split by a \0, else
968 * get_track_name might fail */
969 *(track_names
+ string_index
+ fn_idx
-1) = '\0';
972 else /* request more buffer so that track and filename fit */
973 len
= (avail
- remain
) + MAX_PATH
;
981 if (!free_slide_prio(0))
984 rb
->buflib_buffer_out(&buf_ctx
, &out
);
988 struct track_data
*new_tracks
= (struct track_data
*)(out
+ (uintptr_t)tracks
);
989 unsigned int bytes
= track_count
* sizeof(struct track_data
);
991 rb
->memmove(new_tracks
, tracks
, bytes
);
999 tracks
->sort
= (disc_num
<< 24) + (track_num
<< 14) + track_count
;
1000 tracks
->name_idx
= string_index
;
1001 tracks
->seek
= tcs
.result_seek
;
1002 #if PF_PLAYBACK_CAPABLE
1003 tracks
->filename_idx
= fn_idx
+ string_index
;
1006 string_index
+= len
;
1009 rb
->tagcache_search_finish(&tcs
);
1011 /* now fix the track list order */
1012 rb
->qsort(tracks
, track_count
, sizeof(struct track_data
), compare_tracks
);
1020 Determine filename of the album art for the given slide_index and
1021 store the result in buf.
1022 The algorithm looks for the first track of the given album uses
1023 find_albumart to find the filename.
1025 bool get_albumart_for_index_from_db(const int slide_index
, char *buf
,
1028 if ( slide_index
== -1 )
1030 rb
->strlcpy( buf
, EMPTY_SLIDE
, buflen
);
1033 if (!rb
->tagcache_search(&tcs
, tag_filename
))
1037 /* find the first track of the album */
1038 rb
->tagcache_search_add_filter(&tcs
, tag_album
, album
[slide_index
].seek
);
1040 if ( rb
->tagcache_get_next(&tcs
) ) {
1041 struct mp3entry id3
;
1044 #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
1045 if (rb
->tagcache_fill_tags(&id3
, tcs
.result
))
1047 rb
->strlcpy(id3
.path
, tcs
.result
, sizeof(id3
.path
));
1052 fd
= rb
->open(tcs
.result
, O_RDONLY
);
1053 rb
->get_metadata(&id3
, fd
, tcs
.result
);
1056 if ( search_albumart_files(&id3
, ":", buf
, buflen
) )
1062 /* did not find a matching track */
1065 rb
->tagcache_search_finish(&tcs
);
1070 Draw the PictureFlow logo
1072 void draw_splashscreen(void)
1074 unsigned char * buf_tmp
= buf
;
1075 size_t buf_tmp_size
= buf_size
;
1076 struct screen
* display
= rb
->screens
[SCREEN_MAIN
];
1078 ALIGN_BUFFER(buf_tmp
, buf_tmp_size
, sizeof(fb_data
));
1080 struct bitmap logo
= {
1090 int ret
= rb
->read_bmp_file(SPLASH_BMP
, &logo
, buf_tmp_size
,
1091 FORMAT_NATIVE
, NULL
);
1093 rb
->lcd_set_background(N_BRIGHT(0));
1094 rb
->lcd_set_foreground(N_BRIGHT(255));
1096 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
1098 rb
->lcd_clear_display();
1102 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
1103 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
^ DRMODE_INVERSEVID
);
1105 display
->bitmap(logo
.data
, (LCD_WIDTH
- logo
.width
) / 2, 10,
1106 logo
.width
, logo
.height
);
1107 #if LCD_DEPTH == 1 /* Mono LCDs need the logo inverted */
1108 rb
->lcd_set_drawmode(PICTUREFLOW_DRMODE
);
1117 Draw a simple progress bar
1119 void draw_progressbar(int step
)
1122 const int bar_height
= 22;
1123 const int w
= LCD_WIDTH
- 20;
1126 rb
->lcd_getstringsize("Preparing album artwork", &txt_w
, &txt_h
);
1128 int y
= (LCD_HEIGHT
- txt_h
)/2;
1130 rb
->lcd_putsxy((LCD_WIDTH
- txt_w
)/2, y
, "Preparing album artwork");
1134 rb
->lcd_set_foreground(N_BRIGHT(100));
1136 rb
->lcd_drawrect(x
, y
, w
+2, bar_height
);
1138 rb
->lcd_set_foreground(N_PIX(165, 231, 82));
1141 rb
->lcd_fillrect(x
+1, y
+1, step
* w
/ album_count
, bar_height
-2);
1143 rb
->lcd_set_foreground(N_BRIGHT(255));
1149 /* Calculate modified FNV hash of string
1150 * has good avalanche behaviour and uniform distribution
1151 * see http://home.comcast.net/~bretm/hash/ */
1152 unsigned int mfnv(char *str
)
1154 const unsigned int p
= 16777619;
1155 unsigned int hash
= 0x811C9DC5; // 2166136261;
1158 hash
= (hash
^ *str
++) * p
;
1168 Save the given bitmap as filename in the pfraw format
1170 bool save_pfraw(char* filename
, struct bitmap
*bm
)
1172 struct pfraw_header bmph
;
1173 bmph
.width
= bm
->width
;
1174 bmph
.height
= bm
->height
;
1175 int fh
= rb
->creat( filename
, 0666);
1176 if( fh
< 0 ) return false;
1177 rb
->write( fh
, &bmph
, sizeof( struct pfraw_header
) );
1178 pix_t
*data
= (pix_t
*)( bm
->data
);
1180 for( y
= 0; y
< bm
->height
; y
++ )
1182 rb
->write( fh
, data
, sizeof( pix_t
) * bm
->width
);
1190 Precomupte the album art images and store them in CACHE_PREFIX.
1191 Use the "?" bitmap if image is not found.
1193 bool create_albumart_cache(void)
1198 struct bitmap input_bmp
;
1200 char pfraw_file
[MAX_PATH
];
1201 char albumart_file
[MAX_PATH
];
1202 unsigned int format
= FORMAT_NATIVE
;
1203 bool update
= (cache_version
== CACHE_UPDATE
);
1205 format
|= FORMAT_RESIZE
|FORMAT_KEEP_ASPECT
;
1206 for (i
=0; i
< album_count
; i
++)
1208 draw_progressbar(i
);
1210 rb
->snprintf(pfraw_file
, sizeof(pfraw_file
), CACHE_PREFIX
"/%x.pfraw",
1211 mfnv(get_album_name(i
)));
1212 /* delete existing cache, so it's a true rebuild */
1213 if(rb
->file_exists(pfraw_file
)) {
1218 rb
->remove(pfraw_file
);
1220 if (!get_albumart_for_index_from_db(i
, albumart_file
, MAX_PATH
))
1221 rb
->strcpy(albumart_file
, EMPTY_SLIDE_BMP
);
1223 input_bmp
.data
= buf
;
1224 input_bmp
.width
= DISPLAY_WIDTH
;
1225 input_bmp
.height
= DISPLAY_HEIGHT
;
1226 ret
= read_image_file(albumart_file
, &input_bmp
, buf_size
,
1227 format
, &format_transposed
);
1229 rb
->splashf(HZ
, "Album art is bad: %s", get_album_name(i
));
1230 rb
->strcpy(albumart_file
, EMPTY_SLIDE_BMP
);
1231 ret
= read_image_file(albumart_file
, &input_bmp
, buf_size
,
1232 format
, &format_transposed
);
1236 if (!save_pfraw(pfraw_file
, &input_bmp
))
1238 rb
->splash(HZ
, "Could not write bmp");
1242 if ( rb
->button_get(false) == PF_MENU
) return false;
1244 draw_progressbar(i
);
1245 if ( slides
== 0 ) {
1246 /* Warn the user that we couldn't find any albumart */
1247 rb
->splash(2*HZ
, "No album art found");
1254 Create the "?" slide, that is shown while loading
1255 or when no cover was found.
1257 int create_empty_slide(bool force
)
1259 if ( force
|| ! rb
->file_exists( EMPTY_SLIDE
) ) {
1260 struct bitmap input_bmp
;
1261 input_bmp
.width
= DISPLAY_WIDTH
;
1262 input_bmp
.height
= DISPLAY_HEIGHT
;
1264 input_bmp
.format
= FORMAT_NATIVE
;
1266 input_bmp
.data
= (char*)buf
;
1267 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(, PRIORITY_BUFFERING
)
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 rb
->buflib_free(&buf_ctx
, cache
[i
].hid
);
1462 cache
[i
].index
= -1;
1463 lla_pop_item(&cache_used
, i
);
1464 lla_insert_tail(&cache_free
, i
);
1465 if (cache_used
== -1)
1467 cache_right_index
= -1;
1468 cache_left_index
= -1;
1469 cache_center_index
= -1;
1475 Free one slide ranked above the given priority. If no such slide can be found,
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
;
1526 hid
= rb
->buflib_alloc(&buf_ctx
, size
);
1527 } while (hid
< 0 && free_slide_prio(prio
));
1534 rb
->yield(); /* allow audio to play when fast scrolling */
1535 struct dim
*bm
= rb
->buflib_get_data(&buf_ctx
, hid
);
1537 bm
->width
= bmph
.width
;
1538 bm
->height
= bmph
.height
;
1539 pix_t
*data
= (pix_t
*)(sizeof(struct dim
) + (char *)bm
);
1542 for( y
= 0; y
< bm
->height
; y
++ )
1544 rb
->read( fh
, data
, sizeof( pix_t
) * bm
->width
);
1553 Load the surface for the given slide_index into the cache at cache_index.
1555 static inline bool load_and_prepare_surface(const int slide_index
,
1556 const int cache_index
,
1559 char pfraw_file
[MAX_PATH
];
1560 rb
->snprintf(pfraw_file
, sizeof(pfraw_file
), CACHE_PREFIX
"/%x.pfraw",
1561 mfnv(get_album_name(slide_index
)));
1563 int hid
= read_pfraw(pfraw_file
, prio
);
1567 cache
[cache_index
].hid
= hid
;
1569 if ( cache_index
< SLIDE_CACHE_SIZE
) {
1570 cache
[cache_index
].index
= slide_index
;
1578 Load the "next" slide that we can load, freeing old slides if needed, provided
1579 that they are further from center_index than the current slide
1581 bool load_new_slide(void)
1584 if (cache_center_index
!= -1)
1587 if (cache
[cache_center_index
].index
!= center_index
)
1589 if (cache
[cache_center_index
].index
< center_index
)
1591 cache_center_index
= seek_right_while(cache_center_index
,
1592 cache
[next_
].index
<= center_index
);
1593 prev
= cache_center_index
;
1594 next
= cache
[cache_center_index
].next
;
1598 cache_center_index
= seek_left_while(cache_center_index
,
1599 cache
[next_
].index
>= center_index
);
1600 next
= cache_center_index
;
1601 prev
= cache
[cache_center_index
].prev
;
1603 if (cache
[cache_center_index
].index
!= center_index
)
1605 if (cache_free
== -1)
1607 i
= lla_pop_head(&cache_free
);
1608 if (!load_and_prepare_surface(center_index
, i
, 0))
1609 goto fail_and_refree
;
1610 if (cache
[next
].index
== -1)
1612 if (cache
[prev
].index
== -1)
1613 goto insert_first_slide
;
1615 next
= cache
[prev
].next
;
1617 lla_insert(i
, next
);
1618 if (cache
[i
].index
< cache
[cache_used
].index
)
1620 cache_center_index
= i
;
1621 cache_left_index
= i
;
1622 cache_right_index
= i
;
1626 if (cache
[cache_left_index
].index
>
1627 cache
[cache_center_index
].index
)
1628 cache_left_index
= cache_center_index
;
1629 if (cache
[cache_right_index
].index
<
1630 cache
[cache_center_index
].index
)
1631 cache_right_index
= cache_center_index
;
1632 cache_left_index
= seek_left_while(cache_left_index
,
1633 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1634 cache_right_index
= seek_right_while(cache_right_index
,
1635 cache
[ind_
].index
- 1 == cache
[next_
].index
);
1636 int prio_l
= cache
[cache_center_index
].index
-
1637 cache
[cache_left_index
].index
+ 1;
1638 int prio_r
= cache
[cache_right_index
].index
-
1639 cache
[cache_center_index
].index
+ 1;
1640 if ((prio_l
< prio_r
||
1641 cache
[cache_right_index
].index
>= number_of_slides
) &&
1642 cache
[cache_left_index
].index
> 0)
1644 if (cache_free
== -1 && !free_slide_prio(prio_l
))
1646 i
= lla_pop_head(&cache_free
);
1647 if (load_and_prepare_surface(cache
[cache_left_index
].index
1650 lla_insert_before(&cache_used
, i
, cache_left_index
);
1651 cache_left_index
= i
;
1654 } else if(cache
[cache_right_index
].index
< number_of_slides
- 1)
1656 if (cache_free
== -1 && !free_slide_prio(prio_r
))
1658 i
= lla_pop_head(&cache_free
);
1659 if (load_and_prepare_surface(cache
[cache_right_index
].index
1662 lla_insert_after(i
, cache_right_index
);
1663 cache_right_index
= i
;
1668 i
= lla_pop_head(&cache_free
);
1669 if (load_and_prepare_surface(center_index
, i
, 0))
1674 cache_center_index
= i
;
1675 cache_left_index
= i
;
1676 cache_right_index
= i
;
1684 lla_insert_tail(&cache_free
, i
);
1691 Get a slide from the buffer
1693 static inline struct dim
*get_slide(const int hid
)
1700 bmp
= rb
->buflib_get_data(&buf_ctx
, hid
);
1707 Return the requested surface
1709 static inline struct dim
*surface(const int slide_index
)
1711 if (slide_index
< 0)
1713 if (slide_index
>= number_of_slides
)
1716 if ((i
= cache_used
) != -1)
1719 if (cache
[i
].index
== slide_index
)
1720 return get_slide(cache
[i
].hid
);
1722 } while (i
!= cache_used
);
1724 return get_slide(empty_slide_hid
);
1728 adjust slides so that they are in "steady state" position
1730 void reset_slides(void)
1732 center_slide
.angle
= 0;
1733 center_slide
.cx
= 0;
1734 center_slide
.cy
= 0;
1735 center_slide
.distance
= 0;
1736 center_slide
.slide_index
= center_index
;
1739 for (i
= 0; i
< num_slides
; i
++) {
1740 struct slide_data
*si
= &left_slides
[i
];
1742 si
->cx
= -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
);
1744 si
->slide_index
= center_index
- 1 - i
;
1748 for (i
= 0; i
< num_slides
; i
++) {
1749 struct slide_data
*si
= &right_slides
[i
];
1751 si
->cx
= offsetX
+ slide_spacing
* i
* PFREAL_ONE
;
1753 si
->slide_index
= center_index
+ 1 + i
;
1760 Updates look-up table and other stuff necessary for the rendering.
1761 Call this when the viewport size or slide dimension is changed.
1763 * To calculate the offset that will provide the proper margin, we use the same
1764 * projection used to render the slides. The solution for xc, the slide center,
1766 * xp * (zo + xs * sin(r))
1767 * xc = xp - xs * cos(r) + ───────────────────────
1769 * TODO: support moving the side slides toward or away from the camera
1771 void recalc_offsets(void)
1773 PFreal xs
= PFREAL_HALF
- DISPLAY_WIDTH
* PFREAL_HALF
;
1775 PFreal xp
= (DISPLAY_WIDTH
* PFREAL_HALF
- PFREAL_HALF
+ center_margin
*
1776 PFREAL_ONE
) * zoom
/ 100;
1779 itilt
= 70 * IANGLE_MAX
/ 360; /* approx. 70 degrees tilted */
1780 cosr
= fcos(-itilt
);
1781 sinr
= fsin(-itilt
);
1782 zo
= CAM_DIST_R
* 100 / zoom
- CAM_DIST_R
+
1783 fmuln(MAXSLIDE_LEFT_R
, sinr
, PFREAL_SHIFT
- 2, 0);
1784 offsetX
= xp
- fmul(xs
, cosr
) + fmuln(xp
,
1785 zo
+ fmuln(xs
, sinr
, PFREAL_SHIFT
- 2, 0), PFREAL_SHIFT
- 2, 0)
1787 offsetY
= DISPLAY_WIDTH
/ 2 * (fsin(itilt
) + PFREAL_ONE
/ 2);
1792 Fade the given color by spreading the fb_data (ushort)
1793 to an uint, multiply and compress the result back to a ushort.
1795 #if (LCD_PIXELFORMAT == RGB565SWAPPED)
1796 static inline unsigned fade_color(pix_t c
, unsigned a
)
1798 unsigned int result
;
1800 a
= (a
+ 2) & 0x1fc;
1801 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1802 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1804 return swap16(result
);
1806 #elif LCD_PIXELFORMAT == RGB565
1807 static inline unsigned fade_color(pix_t c
, unsigned a
)
1809 unsigned int result
;
1810 a
= (a
+ 2) & 0x1fc;
1811 result
= ((c
& 0xf81f) * a
) & 0xf81f00;
1812 result
|= ((c
& 0x7e0) * a
) & 0x7e000;
1817 static inline unsigned fade_color(pix_t c
, unsigned a
)
1820 return MULUQ(val
, a
) >> 8;
1825 * Render a single slide
1826 * Where xc is the slide's horizontal offset from center, xs is the horizontal
1827 * on the slide from its center, zo is the slide's depth offset from the plane
1828 * of the display, r is the angle at which the slide is tilted, and xp is the
1829 * point on the display corresponding to xs on the slide, the projection
1832 * z * (xc + xs * cos(r))
1833 * xp = ──────────────────────
1834 * z + zo + xs * sin(r)
1836 * z * (xc - xp) - xp * zo
1837 * xs = ────────────────────────
1838 * xp * sin(r) - z * cos(r)
1840 * We use the xp projection once, to find the left edge of the slide on the
1841 * display. From there, we use the xs reverse projection to find the horizontal
1842 * offset from the slide center of each column on the screen, until we reach
1843 * the right edge of the slide, or the screen. The reverse projection can be
1844 * optimized by saving the numerator and denominator of the fraction, which can
1845 * then be incremented by (z + zo) and sin(r) respectively.
1847 void render_slide(struct slide_data
*slide
, const int alpha
)
1849 struct dim
*bmp
= surface(slide
->slide_index
);
1853 if (slide
->angle
> 255 || slide
->angle
< -255)
1855 pix_t
*src
= (pix_t
*)(sizeof(struct dim
) + (char *)bmp
);
1857 const int sw
= bmp
->width
;
1858 const int sh
= bmp
->height
;
1859 const PFreal slide_left
= -sw
* PFREAL_HALF
+ PFREAL_HALF
;
1860 const int w
= LCD_WIDTH
;
1862 uint8_t reftab
[REFLECT_HEIGHT
]; /* on stack, which is in IRAM on several targets */
1864 if (alpha
== 256) { /* opaque -> copy table */
1865 rb
->memcpy(reftab
, reflect_table
, sizeof(reftab
));
1866 } else { /* precalculate faded table */
1868 for (i
= 0; i
< REFLECT_HEIGHT
; i
++) {
1869 lalpha
= reflect_table
[i
];
1870 reftab
[i
] = (MULUQ(lalpha
, alpha
) + 129) >> 8;
1874 PFreal cosr
= fcos(slide
->angle
);
1875 PFreal sinr
= fsin(slide
->angle
);
1876 PFreal zo
= PFREAL_ONE
* slide
->distance
+ CAM_DIST_R
* 100 / zoom
1877 - CAM_DIST_R
- fmuln(MAXSLIDE_LEFT_R
, fabs(sinr
), PFREAL_SHIFT
- 2, 0);
1878 PFreal xs
= slide_left
, xsnum
, xsnumi
, xsden
, xsdeni
;
1879 PFreal xp
= fdiv(CAM_DIST
* (slide
->cx
+ fmul(xs
, cosr
)),
1880 (CAM_DIST_R
+ zo
+ fmul(xs
,sinr
)));
1882 /* Since we're finding the screen position of the left edge of the slide,
1885 int xi
= (fmax(DISPLAY_LEFT_R
, xp
) - DISPLAY_LEFT_R
+ PFREAL_ONE
- 1)
1887 xp
= DISPLAY_LEFT_R
+ xi
* PFREAL_ONE
;
1891 xsnum
= CAM_DIST
* (slide
->cx
- xp
) - fmuln(xp
, zo
, PFREAL_SHIFT
- 2, 0);
1892 xsden
= fmuln(xp
, sinr
, PFREAL_SHIFT
- 2, 0) - CAM_DIST
* cosr
;
1893 xs
= fdiv(xsnum
, xsden
);
1895 xsnumi
= -CAM_DIST_R
- zo
;
1898 int dy
= PFREAL_ONE
;
1899 for (x
= xi
; x
< w
; x
++) {
1900 int column
= (xs
- slide_left
) / PFREAL_ONE
;
1903 if (zo
|| slide
->angle
)
1904 dy
= (CAM_DIST_R
+ zo
+ fmul(xs
, sinr
)) / CAM_DIST
;
1906 const pix_t
*ptr
= &src
[column
* bmp
->height
];
1908 #if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
1909 #define PIXELSTEP_Y 1
1910 #define LCDADDR(x, y) (&buffer[BUFFER_HEIGHT*(x) + (y)])
1912 #define PIXELSTEP_Y BUFFER_WIDTH
1913 #define LCDADDR(x, y) (&buffer[(y)*BUFFER_WIDTH + (x)])
1916 int p
= (bmp
->height
-1-DISPLAY_OFFS
) * PFREAL_ONE
;
1917 int plim
= MAX(0, p
- (LCD_HEIGHT
/2-1) * dy
);
1918 pix_t
*pixel
= LCDADDR(x
, (LCD_HEIGHT
/2)-1 );
1922 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1924 pixel
-= PIXELSTEP_Y
;
1928 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1930 pixel
-= PIXELSTEP_Y
;
1933 p
= (bmp
->height
-DISPLAY_OFFS
) * PFREAL_ONE
;
1934 plim
= MIN(sh
* PFREAL_ONE
, p
+ (LCD_HEIGHT
/2) * dy
);
1935 int plim2
= MIN(MIN(sh
+ REFLECT_HEIGHT
, sh
* 2) * PFREAL_ONE
,
1936 p
+ (LCD_HEIGHT
/2) * dy
);
1937 pixel
= LCDADDR(x
, (LCD_HEIGHT
/2) );
1941 *pixel
= ptr
[((unsigned)p
) >> PFREAL_SHIFT
];
1943 pixel
+= PIXELSTEP_Y
;
1947 *pixel
= fade_color(ptr
[((unsigned)p
) >> PFREAL_SHIFT
], alpha
);
1949 pixel
+= PIXELSTEP_Y
;
1953 int ty
= (((unsigned)p
) >> PFREAL_SHIFT
) - sh
;
1954 int lalpha
= reftab
[ty
];
1955 *pixel
= fade_color(ptr
[sh
- 1 - ty
], lalpha
);
1957 pixel
+= PIXELSTEP_Y
;
1960 if (zo
|| slide
->angle
)
1964 xs
= fdiv(xsnum
, xsden
);
1969 /* let the music play... */
1975 Jump the the given slide_index
1977 static inline void set_current_slide(const int slide_index
)
1979 int old_center_index
= center_index
;
1981 center_index
= fbound(slide_index
, 0, number_of_slides
- 1);
1982 if (old_center_index
!= center_index
)
1983 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
1984 target
= center_index
;
1985 slide_frame
= slide_index
<< 16;
1990 Start the animation for changing slides
1992 void start_animation(void)
1994 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
1995 pf_state
= pf_scrolling
;
1999 Go to the previous slide
2001 void show_previous_slide(void)
2004 if (center_index
> 0) {
2005 target
= center_index
- 1;
2008 } else if ( step
> 0 ) {
2009 target
= center_index
;
2010 step
= (target
<= center_slide
.slide_index
) ? -1 : 1;
2012 target
= fmax(0, center_index
- 2);
2018 Go to the next slide
2020 void show_next_slide(void)
2023 if (center_index
< number_of_slides
- 1) {
2024 target
= center_index
+ 1;
2027 } else if ( step
< 0 ) {
2028 target
= center_index
;
2029 step
= (target
< center_slide
.slide_index
) ? -1 : 1;
2031 target
= fmin(center_index
+ 2, number_of_slides
- 1);
2037 Render the slides. Updates only the offscreen buffer.
2039 void render_all_slides(void)
2041 mylcd_set_background(G_BRIGHT(0));
2042 /* TODO: Optimizes this by e.g. invalidating rects */
2043 mylcd_clear_display();
2045 int nleft
= num_slides
;
2046 int nright
= num_slides
;
2051 /* no animation, boring plain rendering */
2052 for (index
= nleft
- 2; index
>= 0; index
--) {
2053 alpha
= (index
< nleft
- 2) ? 256 : 128;
2054 alpha
-= extra_fade
;
2056 render_slide(&left_slides
[index
], alpha
);
2058 for (index
= nright
- 2; index
>= 0; index
--) {
2059 alpha
= (index
< nright
- 2) ? 256 : 128;
2060 alpha
-= extra_fade
;
2062 render_slide(&right_slides
[index
], alpha
);
2065 /* the first and last slide must fade in/fade out */
2067 /* if step<0 and nleft==1, left_slides[0] is fading in */
2068 alpha
= ((step
> 0) ? 0 : ((nleft
== 1) ? 256 : 128)) - fade
/ 2;
2069 for (index
= nleft
- 1; index
>= 0; index
--) {
2071 render_slide(&left_slides
[index
], alpha
);
2073 if (alpha
> 256) alpha
= 256;
2075 /* if step>0 and nright==1, right_slides[0] is fading in */
2076 alpha
= ((step
> 0) ? ((nright
== 1) ? 128 : 0) : -128) + fade
/ 2;
2077 for (index
= nright
- 1; index
>= 0; index
--) {
2079 render_slide(&right_slides
[index
], alpha
);
2081 if (alpha
> 256) alpha
= 256;
2085 if (step
!= 0 && num_slides
<= 2) /* fading out center slide */
2086 alpha
= (step
> 0) ? 256 - fade
/ 2 : 128 + fade
/ 2;
2087 render_slide(¢er_slide
, alpha
);
2092 Updates the animation effect. Call this periodically from a timer.
2094 void update_scroll_animation(void)
2102 /* deaccelerate when approaching the target */
2104 const int max
= 2 * 65536;
2106 int fi
= slide_frame
;
2107 fi
-= (target
<< 16);
2112 int ia
= IANGLE_MAX
* (fi
- max
/ 2) / (max
* 2);
2113 speed
= 512 + 16384 * (PFREAL_ONE
+ fsin(ia
)) / PFREAL_ONE
;
2116 slide_frame
+= speed
* step
;
2118 int index
= slide_frame
>> 16;
2119 int pos
= slide_frame
& 0xffff;
2120 int neg
= 65536 - pos
;
2121 int tick
= (step
< 0) ? neg
: pos
;
2122 PFreal ftick
= (tick
* PFREAL_ONE
) >> 16;
2124 /* the leftmost and rightmost slide must fade away */
2129 if (center_index
!= index
) {
2130 center_index
= index
;
2131 rb
->queue_post(&thread_q
, EV_WAKEUP
, 0);
2132 slide_frame
= index
<< 16;
2133 center_slide
.slide_index
= center_index
;
2134 for (i
= 0; i
< num_slides
; i
++)
2135 left_slides
[i
].slide_index
= center_index
- 1 - i
;
2136 for (i
= 0; i
< num_slides
; i
++)
2137 right_slides
[i
].slide_index
= center_index
+ 1 + i
;
2140 center_slide
.angle
= (step
* tick
* itilt
) >> 16;
2141 center_slide
.cx
= -step
* fmul(offsetX
, ftick
);
2142 center_slide
.cy
= fmul(offsetY
, ftick
);
2144 if (center_index
== target
) {
2147 slide_frame
= center_index
<< 16;
2153 for (i
= 0; i
< num_slides
; i
++) {
2154 struct slide_data
*si
= &left_slides
[i
];
2157 -(offsetX
+ slide_spacing
* i
* PFREAL_ONE
+ step
2158 * slide_spacing
* ftick
);
2162 for (i
= 0; i
< num_slides
; i
++) {
2163 struct slide_data
*si
= &right_slides
[i
];
2166 offsetX
+ slide_spacing
* i
* PFREAL_ONE
- step
2167 * slide_spacing
* ftick
;
2172 PFreal ftick
= (neg
* PFREAL_ONE
) >> 16;
2173 right_slides
[0].angle
= -(neg
* itilt
) >> 16;
2174 right_slides
[0].cx
= fmul(offsetX
, ftick
);
2175 right_slides
[0].cy
= fmul(offsetY
, ftick
);
2177 PFreal ftick
= (pos
* PFREAL_ONE
) >> 16;
2178 left_slides
[0].angle
= (pos
* itilt
) >> 16;
2179 left_slides
[0].cx
= -fmul(offsetX
, ftick
);
2180 left_slides
[0].cy
= fmul(offsetY
, ftick
);
2183 /* must change direction ? */
2198 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2199 rb
->cpu_boost(false);
2202 /* Turn on backlight timeout (revert to settings) */
2203 backlight_use_settings();
2211 Shows the settings menu
2213 int settings_menu(void)
2218 MENUITEM_STRINGLIST(settings_menu
, "PictureFlow Settings", NULL
, "Show FPS",
2219 "Spacing", "Centre margin", "Number of slides", "Zoom",
2220 "Show album title", "Resize Covers", "Rebuild cache",
2221 "WPS Integration", "Backlight");
2223 static const struct opt_items album_name_options
[] = {
2224 { "Hide album title", -1 },
2225 { "Show at the bottom", -1 },
2226 { "Show at the top", -1 }
2228 static const struct opt_items wps_options
[] = {
2231 { "Via Track list", -1 }
2233 static const struct opt_items backlight_options
[] = {
2234 { "Always On", -1 },
2239 selection
=rb
->do_menu(&settings_menu
,&selection
, NULL
, false);
2242 rb
->set_bool("Show FPS", &show_fps
);
2247 rb
->set_int("Spacing between slides", "", 1,
2249 NULL
, 1, 0, 100, NULL
);
2255 rb
->set_int("Centre margin", "", 1,
2257 NULL
, 1, 0, 80, NULL
);
2263 rb
->set_int("Number of slides", "", 1, &num_slides
,
2264 NULL
, 1, 1, MAX_SLIDES_COUNT
, NULL
);
2270 rb
->set_int("Zoom", "", 1, &zoom
,
2271 NULL
, 1, 10, 300, NULL
);
2276 rb
->set_option("Show album title", &show_album_name
,
2277 INT
, album_name_options
, 3, NULL
);
2284 rb
->set_bool("Resize Covers", &resize
);
2285 if (old_val
== resize
) /* changed? */
2287 /* fallthrough if changed, since cache needs to be rebuilt */
2289 cache_version
= CACHE_REBUILD
;
2290 rb
->remove(EMPTY_SLIDE
);
2291 configfile_save(CONFIG_FILE
, config
,
2292 CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2293 rb
->splash(HZ
, "Cache will be rebuilt on next restart");
2296 rb
->set_option("WPS Integration", &auto_wps
, INT
, wps_options
, 3, NULL
);
2299 rb
->set_option("Backlight", &backlight_mode
, INT
, backlight_options
, 2, NULL
);
2302 case MENU_ATTACHED_USB
:
2303 return PLUGIN_USB_CONNECTED
;
2305 } while ( selection
>= 0 );
2314 #if PF_PLAYBACK_CAPABLE
2315 PF_MENU_CLEAR_PLAYLIST
,
2316 PF_MENU_PLAYBACK_CONTROL
,
2329 rb
->lcd_set_foreground(N_BRIGHT(255));
2332 MENUITEM_STRINGLIST(main_menu
,"PictureFlow Main Menu",NULL
,
2334 #if PF_PLAYBACK_CAPABLE
2335 "Clear playlist", "Playback Control",
2337 "Settings", "Return", "Quit");
2339 switch (rb
->do_menu(&main_menu
,&selection
, NULL
, false)) {
2340 case PF_GOTO_WPS
: /* WPS */
2342 #if PF_PLAYBACK_CAPABLE
2343 case PF_MENU_CLEAR_PLAYLIST
:
2344 if(rb
->playlist_remove_all_tracks(NULL
) == 0) {
2345 rb
->playlist_create(NULL
, NULL
);
2346 rb
->splash(HZ
*2, "Playlist Cleared");
2349 case PF_MENU_PLAYBACK_CONTROL
: /* Playback Control */
2350 playback_control(NULL
);
2353 case PF_MENU_SETTINGS
:
2354 result
= settings_menu();
2355 if ( result
!= 0 ) return result
;
2357 case PF_MENU_RETURN
:
2362 case MENU_ATTACHED_USB
:
2363 return PLUGIN_USB_CONNECTED
;
2372 Animation step for zooming into the current cover
2374 void update_cover_in_animation(void)
2376 cover_animation_keyframe
++;
2377 if( cover_animation_keyframe
< 20 ) {
2378 center_slide
.distance
-=5;
2379 center_slide
.angle
+=1;
2382 else if( cover_animation_keyframe
< 35 ) {
2383 center_slide
.angle
+=16;
2386 cover_animation_keyframe
= 0;
2387 pf_state
= pf_show_tracks
;
2392 Animation step for zooming out the current cover
2394 void update_cover_out_animation(void)
2396 cover_animation_keyframe
++;
2397 if( cover_animation_keyframe
<= 15 ) {
2398 center_slide
.angle
-=16;
2400 else if( cover_animation_keyframe
< 35 ) {
2401 center_slide
.distance
+=5;
2402 center_slide
.angle
-=1;
2406 cover_animation_keyframe
= 0;
2412 Draw a blue gradient at y with height h
2414 static inline void draw_gradient(int y
, int h
)
2417 inc
= (100 << 8) / h
;
2419 selected_track_pulse
= (selected_track_pulse
+1) % 10;
2420 int c2
= selected_track_pulse
- 5;
2421 for (r
=0; r
<h
; r
++) {
2422 #ifdef HAVE_LCD_COLOR
2423 mylcd_set_foreground(G_PIX(c2
+80-(c
>> 9), c2
+100-(c
>> 9),
2426 mylcd_set_foreground(G_BRIGHT(c2
+160-(c
>> 8)));
2428 mylcd_hline(0, LCD_WIDTH
, r
+y
);
2437 static void track_list_yh(int char_height
)
2439 switch (show_album_name
)
2441 case ALBUM_NAME_HIDE
:
2442 track_list_y
= (show_fps
? char_height
: 0);
2443 track_list_h
= LCD_HEIGHT
- track_list_y
;
2445 case ALBUM_NAME_BOTTOM
:
2446 track_list_y
= (show_fps
? char_height
: 0);
2447 track_list_h
= LCD_HEIGHT
- track_list_y
- char_height
* 2;
2449 case ALBUM_NAME_TOP
:
2451 track_list_y
= char_height
* 2;
2452 track_list_h
= LCD_HEIGHT
- track_list_y
-
2453 (show_fps
? char_height
: 0);
2459 Reset the track list after a album change
2461 void reset_track_list(void)
2463 int char_height
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2465 track_list_yh(char_height
);
2466 track_list_visible_entries
= fmin( track_list_h
/char_height
, track_count
);
2467 start_index_track_list
= 0;
2469 last_selected_track
= -1;
2471 /* let the tracklist start more centered
2472 * if the screen isn't filled with tracks */
2473 total_height
= track_count
*char_height
;
2474 if (total_height
< track_list_h
)
2476 track_list_y
+= (track_list_h
- total_height
) / 2;
2477 track_list_h
= total_height
;
2482 Display the list of tracks
2484 void show_track_list(void)
2486 mylcd_clear_display();
2487 if ( center_slide
.slide_index
!= track_index
) {
2488 create_track_index(center_slide
.slide_index
);
2491 int titletxt_w
, titletxt_x
, color
, titletxt_h
;
2492 titletxt_h
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2494 int titletxt_y
= track_list_y
;
2496 track_i
= start_index_track_list
;
2497 for (;track_i
< track_list_visible_entries
+start_index_track_list
;
2500 char *trackname
= get_track_name(track_i
);
2501 if ( track_i
== selected_track
) {
2502 if (selected_track
!= last_selected_track
) {
2503 set_scroll_line(trackname
, PF_SCROLL_TRACK
);
2504 last_selected_track
= selected_track
;
2506 draw_gradient(titletxt_y
, titletxt_h
);
2507 titletxt_x
= get_scroll_line_offset(PF_SCROLL_TRACK
);
2511 titletxt_w
= mylcd_getstringsize(trackname
, NULL
, NULL
);
2512 titletxt_x
= (LCD_WIDTH
-titletxt_w
)/2;
2513 color
= 250 - (abs(selected_track
- track_i
) * 200 / track_count
);
2515 mylcd_set_foreground(G_BRIGHT(color
));
2516 mylcd_putsxy(titletxt_x
,titletxt_y
,trackname
);
2517 titletxt_y
+= titletxt_h
;
2521 void select_next_track(void)
2523 if ( selected_track
< track_count
- 1 ) {
2525 if (selected_track
==(track_list_visible_entries
+start_index_track_list
))
2526 start_index_track_list
++;
2530 void select_prev_track(void)
2532 if (selected_track
> 0 ) {
2533 if (selected_track
==start_index_track_list
) start_index_track_list
--;
2538 #if PF_PLAYBACK_CAPABLE
2540 * Puts the current tracklist into a newly created playlist and starts playling
2542 void start_playback(bool append
)
2544 static int old_playlist
= -1, old_shuffle
= 0;
2546 int position
= selected_track
;
2547 int shuffle
= rb
->global_settings
->playlist_shuffle
;
2548 /* reuse existing playlist if possible
2549 * regenerate if shuffle is on or changed, since playlist index and
2550 * selected track are "out of sync" */
2551 if (!shuffle
&& !append
&& center_slide
.slide_index
== old_playlist
2552 && (old_shuffle
== shuffle
))
2556 /* First, replace the current playlist with a new one */
2557 else if (append
|| (rb
->playlist_remove_all_tracks(NULL
) == 0
2558 && rb
->playlist_create(NULL
, NULL
) == 0))
2562 if (rb
->playlist_insert_track(NULL
, get_track_filename(count
),
2563 PLAYLIST_INSERT_LAST
, false, true) < 0)
2565 } while(++count
< track_count
);
2566 rb
->playlist_sync(NULL
);
2571 if (rb
->global_settings
->playlist_shuffle
)
2572 position
= rb
->playlist_shuffle(*rb
->current_tick
, selected_track
);
2574 /* TODO: can we adjust selected_track if !play_selected ?
2575 * if shuffle, we can't predict the playing track easily, and for either
2576 * case the track list doesn't get auto scrolled*/
2578 rb
->playlist_start(position
, 0);
2579 old_playlist
= center_slide
.slide_index
;
2580 old_shuffle
= shuffle
;
2585 Draw the current album name
2587 void draw_album_text(void)
2589 if (show_album_name
== ALBUM_NAME_HIDE
)
2594 int albumtxt_x
, albumtxt_y
;
2598 /* Draw album text */
2599 if ( pf_state
== pf_scrolling
) {
2600 c
= ((slide_frame
& 0xffff )/ 255);
2601 if (step
< 0) c
= 255-c
;
2602 if (c
> 128 ) { /* half way to next slide .. still not perfect! */
2603 albumtxt_index
= center_index
+step
;
2607 albumtxt_index
= center_index
;
2612 albumtxt_index
= center_index
;
2615 albumtxt
= get_album_name(albumtxt_index
);
2617 mylcd_set_foreground(G_BRIGHT(c
));
2618 if (albumtxt_index
!= prev_albumtxt_index
) {
2619 set_scroll_line(albumtxt
, PF_SCROLL_ALBUM
);
2620 prev_albumtxt_index
= albumtxt_index
;
2623 char_height
= rb
->screens
[SCREEN_MAIN
]->getcharheight();
2624 if (show_album_name
== ALBUM_NAME_TOP
)
2625 albumtxt_y
= char_height
/ 2;
2627 albumtxt_y
= LCD_HEIGHT
- char_height
- char_height
/2;
2629 albumtxt_x
= get_scroll_line_offset(PF_SCROLL_ALBUM
);
2630 mylcd_putsxy(albumtxt_x
, albumtxt_y
, albumtxt
);
2634 Display an error message and wait for input.
2636 void error_wait(const char *message
)
2638 rb
->splashf(0, "%s. Press any button to continue.", message
);
2639 while (rb
->get_action(CONTEXT_STD
, 1) == ACTION_NONE
)
2645 Main function that also contain the main plasma
2652 rb
->lcd_setfont(FONT_UI
);
2654 if ( ! rb
->dir_exists( CACHE_PREFIX
) ) {
2655 if ( rb
->mkdir( CACHE_PREFIX
) < 0 ) {
2656 error_wait("Could not create directory " CACHE_PREFIX
);
2657 return PLUGIN_ERROR
;
2661 configfile_load(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2663 draw_splashscreen();
2664 if(backlight_mode
== 0) {
2665 /* Turn off backlight timeout */
2666 backlight_ignore_timeout();
2669 init_scroll_lines();
2670 init_reflect_table();
2672 ALIGN_BUFFER(buf
, buf_size
, 4);
2673 ret
= create_album_index();
2674 if (ret
== ERROR_BUFFER_FULL
) {
2675 error_wait("Not enough memory for album names");
2676 return PLUGIN_ERROR
;
2677 } else if (ret
== ERROR_NO_ALBUMS
) {
2678 error_wait("No albums found. Please enable database");
2679 return PLUGIN_ERROR
;
2682 ALIGN_BUFFER(buf
, buf_size
, 4);
2683 number_of_slides
= album_count
;
2684 if ((cache_version
!= CACHE_VERSION
) && !create_albumart_cache()) {
2685 cache_version
= CACHE_REBUILD
;
2686 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2687 error_wait("Could not create album art cache");
2688 return PLUGIN_ERROR
;
2691 if (!create_empty_slide(cache_version
!= CACHE_VERSION
)) {
2692 cache_version
= CACHE_REBUILD
;
2693 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2694 error_wait("Could not load the empty slide");
2695 return PLUGIN_ERROR
;
2697 if (cache_version
!= CACHE_VERSION
)
2699 cache_version
= CACHE_VERSION
;
2700 configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
, CONFIG_VERSION
);
2703 rb
->buflib_init(&buf_ctx
, (void *)buf
, buf_size
);
2705 if (!(empty_slide_hid
= read_pfraw(EMPTY_SLIDE
, 0)))
2707 error_wait("Unable to load empty slide image");
2708 return PLUGIN_ERROR
;
2711 if (!create_pf_thread()) {
2712 error_wait("Cannot create thread!");
2713 return PLUGIN_ERROR
;
2719 for (i
= 0; i
< SLIDE_CACHE_SIZE
; i
++) {
2722 cache
[i
].next
= i
+ 1;
2723 cache
[i
].prev
= i
- 1;
2725 cache
[0].prev
= i
- 1;
2726 cache
[i
- 1].next
= 0;
2741 set_current_slide(get_wps_current_index());
2747 long last_update
= *rb
->current_tick
;
2748 long current_update
;
2749 long update_interval
= 100;
2753 bool instant_update
;
2756 grey_set_drawmode(DRMODE_FG
);
2758 rb
->lcd_set_drawmode(DRMODE_FG
);
2760 current_update
= *rb
->current_tick
;
2763 /* Initial rendering */
2764 instant_update
= false;
2766 update_scroll_lines();
2769 switch ( pf_state
) {
2771 update_scroll_animation();
2772 render_all_slides();
2773 instant_update
= true;
2776 update_cover_in_animation();
2777 render_all_slides();
2778 instant_update
= true;
2781 update_cover_out_animation();
2782 render_all_slides();
2783 instant_update
= true;
2785 case pf_show_tracks
:
2789 render_all_slides();
2794 if (current_update
- last_update
> update_interval
) {
2795 fps
= frames
* HZ
/ (current_update
- last_update
);
2796 last_update
= current_update
;
2803 mylcd_set_foreground(G_BRIGHT(255));
2805 mylcd_set_foreground(G_PIX(255,0,0));
2807 rb
->snprintf(fpstxt
, sizeof(fpstxt
), "FPS: %d", fps
);
2808 if (show_album_name
== ALBUM_NAME_TOP
)
2809 fpstxt_y
= LCD_HEIGHT
-
2810 rb
->screens
[SCREEN_MAIN
]->getcharheight();
2813 mylcd_putsxy(0, fpstxt_y
, fpstxt
);
2818 /* Copy offscreen buffer to LCD and give time to other threads */
2822 /*/ Handle buttons */
2823 button
= rb
->get_custom_action(CONTEXT_PLUGIN
2824 #ifndef USE_CORE_PREVNEXT
2825 |(pf_state
== pf_show_tracks
? 1 : 0)
2827 ,instant_update
? 0 : HZ
/16,
2834 return PLUGIN_GOTO_WPS
;
2836 if ( pf_state
== pf_show_tracks
)
2838 rb
->buflib_buffer_in(&buf_ctx
, borrowed
);
2841 pf_state
= pf_cover_out
;
2843 if (pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2851 if ( ret
== -2 ) return PLUGIN_GOTO_WPS
;
2852 if ( ret
== -1 ) return PLUGIN_OK
;
2853 if ( ret
!= 0 ) return ret
;
2857 mylcd_set_drawmode(DRMODE_FG
);
2861 case PF_NEXT_REPEAT
:
2862 if ( pf_state
== pf_show_tracks
)
2863 select_next_track();
2864 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2869 case PF_PREV_REPEAT
:
2870 if ( pf_state
== pf_show_tracks
)
2871 select_prev_track();
2872 if ( pf_state
== pf_idle
|| pf_state
== pf_scrolling
)
2873 show_previous_slide();
2875 #if PF_PLAYBACK_CAPABLE
2877 if ( auto_wps
!= 0 ) {
2878 if( pf_state
== pf_idle
) {
2879 create_track_index(center_slide
.slide_index
);
2881 start_playback(true);
2882 rb
->splash(HZ
*2, "Added to playlist");
2884 else if( pf_state
== pf_show_tracks
) {
2885 rb
->playlist_insert_track(NULL
, get_track_filename(selected_track
),
2886 PLAYLIST_INSERT_LAST
, false, true);
2887 rb
->playlist_sync(NULL
);
2888 rb
->splash(HZ
*2, "Added to playlist");
2894 if ( auto_wps
== 1 && pf_state
== pf_idle
) {
2895 pf_state
= pf_cover_in
;
2899 if ( pf_state
== pf_idle
) {
2900 #if PF_PLAYBACK_CAPABLE
2902 create_track_index(center_slide
.slide_index
);
2904 start_playback(false);
2905 last_album
= center_index
;
2906 return PLUGIN_GOTO_WPS
;
2910 pf_state
= pf_cover_in
;
2912 else if ( pf_state
== pf_show_tracks
) {
2913 #if PF_PLAYBACK_CAPABLE
2914 start_playback(false);
2916 last_album
= center_index
;
2917 return PLUGIN_GOTO_WPS
;
2923 exit_on_usb(button
);
2929 /*************************** Plugin entry point ****************************/
2931 enum plugin_status
plugin_start(const void *parameter
)
2937 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
2938 rb
->cpu_boost(true);
2940 #if PF_PLAYBACK_CAPABLE
2941 buf
= rb
->plugin_get_buffer(&buf_size
);
2943 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
2945 if ((uintptr_t)buf
< (uintptr_t)plugin_start_addr
)
2947 uint32_t tmp_size
= (uintptr_t)plugin_start_addr
- (uintptr_t)buf
;
2948 buf_size
= MIN(buf_size
, tmp_size
);
2955 if (!grey_init(buf
, buf_size
, GREY_BUFFERED
|GREY_ON_COP
,
2956 LCD_WIDTH
, LCD_HEIGHT
, &grey_buf_used
))
2958 error_wait("Greylib init failed!");
2959 return PLUGIN_ERROR
;
2961 grey_setfont(FONT_UI
);
2962 buf_size
-= grey_buf_used
;
2963 buf
= (void*)(grey_buf_used
+ (char*)buf
);
2967 if ( ret
== PLUGIN_OK
|| ret
== PLUGIN_GOTO_WPS
) {
2968 if (configfile_save(CONFIG_FILE
, config
, CONFIG_NUM_ITEMS
,
2971 rb
->splash(HZ
, "Error writing config.");