Fix for ff/rw in long MP3 files.
[kugel-rb.git] / apps / wps.c
blob630262600443289e4031a9cf3ed020df282e0b64
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Jerome Kuptz
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
23 #include "system.h"
24 #include "file.h"
25 #include "lcd.h"
26 #include "font.h"
27 #include "backlight.h"
28 #include "button.h"
29 #include "kernel.h"
30 #include "tree.h"
31 #include "debug.h"
32 #include "sprintf.h"
33 #include "settings.h"
34 #include "wps.h"
35 #include "wps-display.h"
36 #include "audio.h"
37 #include "mp3_playback.h"
38 #include "usb.h"
39 #include "status.h"
40 #include "main_menu.h"
41 #include "ata.h"
42 #include "screens.h"
43 #include "playlist.h"
44 #ifdef HAVE_LCD_BITMAP
45 #include "icons.h"
46 #include "peakmeter.h"
47 #endif
48 #include "action.h"
49 #include "lang.h"
50 #include "bookmark.h"
51 #include "misc.h"
52 #include "sound.h"
53 #include "onplay.h"
55 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
56 /* 3% of 30min file == 54s step size */
57 #define MIN_FF_REWIND_STEP 500
59 bool keys_locked = false;
60 static bool ff_rewind = false;
61 static bool paused = false;
62 static struct mp3entry* id3 = NULL;
63 static struct mp3entry* nid3 = NULL;
64 static char current_track_path[MAX_PATH+1];
66 /* set volume
67 return true if screen restore is needed
68 return false otherwise
70 static bool setvol(void)
72 if (global_settings.volume < sound_min(SOUND_VOLUME))
73 global_settings.volume = sound_min(SOUND_VOLUME);
74 if (global_settings.volume > sound_max(SOUND_VOLUME))
75 global_settings.volume = sound_max(SOUND_VOLUME);
76 sound_set(SOUND_VOLUME, global_settings.volume);
77 status_draw(false);
78 wps_refresh(id3, nid3, 0, WPS_REFRESH_NON_STATIC);
79 settings_save();
80 #ifdef HAVE_LCD_CHARCELLS
81 splash(0, false, "Vol: %d %% ",
82 sound_val2phys(SOUND_VOLUME, global_settings.volume));
83 return true;
84 #endif
85 return false;
88 static bool ffwd_rew(int button)
90 static const int ff_rew_steps[] = {
91 1000, 2000, 3000, 4000,
92 5000, 6000, 8000, 10000,
93 15000, 20000, 25000, 30000,
94 45000, 60000
97 unsigned int step = 0; /* current ff/rewind step */
98 unsigned int max_step = 0; /* maximum ff/rewind step */
99 int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */
100 int direction = -1; /* forward=1 or backward=-1 */
101 long accel_tick = 0; /* next time at which to bump the step size */
102 bool exit = false;
103 bool usb = false;
105 while (!exit) {
106 switch ( button ) {
107 case WPS_FFWD:
108 #ifdef WPS_RC_FFWD
109 case WPS_RC_FFWD:
110 #endif
111 direction = 1;
112 case WPS_REW:
113 #ifdef WPS_RC_REW
114 case WPS_RC_REW:
115 #endif
116 if (ff_rewind)
118 if (direction == 1)
120 /* fast forwarding, calc max step relative to end */
121 max_step =
122 (id3->length - (id3->elapsed + ff_rewind_count)) *
123 FF_REWIND_MAX_PERCENT / 100;
125 else
127 /* rewinding, calc max step relative to start */
128 max_step = (id3->elapsed + ff_rewind_count) *
129 FF_REWIND_MAX_PERCENT / 100;
132 max_step = MAX(max_step, MIN_FF_REWIND_STEP);
134 if (step > max_step)
135 step = max_step;
137 ff_rewind_count += step * direction;
139 if (global_settings.ff_rewind_accel != 0 &&
140 current_tick >= accel_tick)
142 step *= 2;
143 accel_tick = current_tick +
144 global_settings.ff_rewind_accel*HZ;
147 else
149 if ( (audio_status() & AUDIO_STATUS_PLAY) &&
150 id3 && id3->length )
152 if (!paused)
153 audio_pause();
154 #if CONFIG_KEYPAD == PLAYER_PAD
155 lcd_stop_scroll();
156 #endif
157 if (direction > 0)
158 status_set_ffmode(STATUS_FASTFORWARD);
159 else
160 status_set_ffmode(STATUS_FASTBACKWARD);
162 ff_rewind = true;
164 step = ff_rew_steps[global_settings.ff_rewind_min_step];
166 accel_tick = current_tick +
167 global_settings.ff_rewind_accel*HZ;
169 else
170 break;
173 if (direction > 0) {
174 if ((id3->elapsed + ff_rewind_count) > id3->length)
175 ff_rewind_count = id3->length - id3->elapsed;
177 else {
178 if ((int)(id3->elapsed + ff_rewind_count) < 0)
179 ff_rewind_count = -id3->elapsed;
182 if(wps_time_countup == false)
183 wps_refresh(id3, nid3, -ff_rewind_count,
184 WPS_REFRESH_PLAYER_PROGRESS |
185 WPS_REFRESH_DYNAMIC);
186 else
187 wps_refresh(id3, nid3, ff_rewind_count,
188 WPS_REFRESH_PLAYER_PROGRESS |
189 WPS_REFRESH_DYNAMIC);
191 break;
193 case WPS_PREV:
194 case WPS_NEXT:
195 #ifdef WPS_RC_PREV
196 case WPS_RC_PREV:
197 case WPS_RC_NEXT:
198 #endif
199 audio_ff_rewind(id3->elapsed+ff_rewind_count);
200 ff_rewind_count = 0;
201 ff_rewind = false;
202 status_set_ffmode(0);
203 if (!paused)
204 audio_resume();
205 #ifdef HAVE_LCD_CHARCELLS
206 wps_display(id3, nid3);
207 #endif
208 exit = true;
209 break;
211 default:
212 if(default_event_handler(button) == SYS_USB_CONNECTED) {
213 status_set_ffmode(0);
214 usb = true;
215 exit = true;
217 break;
219 if (!exit)
220 button = button_get(true);
223 /* let audio thread update id3->elapsed before calling wps_refresh */
224 yield();
225 wps_refresh(id3, nid3, 0, WPS_REFRESH_ALL);
226 return usb;
229 static bool update(void)
231 bool track_changed = audio_has_changed_track();
232 bool retcode = false;
234 nid3 = audio_next_track();
235 if (track_changed)
237 lcd_stop_scroll();
238 id3 = audio_current_track();
239 if (wps_display(id3, nid3))
240 retcode = true;
241 else
242 wps_refresh(id3, nid3, 0, WPS_REFRESH_ALL);
244 if (id3)
245 memcpy(current_track_path, id3->path, sizeof(current_track_path));
248 if (id3)
249 wps_refresh(id3, nid3, 0, WPS_REFRESH_NON_STATIC);
251 status_draw(false);
253 return retcode;
256 static void fade(bool fade_in)
258 unsigned fp_global_vol = global_settings.volume << 8;
259 unsigned fp_step = fp_global_vol / 30;
261 if (fade_in) {
262 /* fade in */
263 unsigned fp_volume = 0;
265 /* zero out the sound */
266 sound_set(SOUND_VOLUME, 0);
268 sleep(HZ/10); /* let audio thread run */
269 audio_resume();
271 while (fp_volume < fp_global_vol) {
272 fp_volume += fp_step;
273 sleep(1);
274 sound_set(SOUND_VOLUME, fp_volume >> 8);
276 sound_set(SOUND_VOLUME, global_settings.volume);
278 else {
279 /* fade out */
280 unsigned fp_volume = fp_global_vol;
282 while (fp_volume > fp_step) {
283 fp_volume -= fp_step;
284 sleep(1);
285 sound_set(SOUND_VOLUME, fp_volume >> 8);
287 audio_pause();
288 #ifndef SIMULATOR
289 /* let audio thread run and wait for the mas to run out of data */
290 while (!mp3_pause_done())
291 #endif
292 sleep(HZ/10);
294 /* reset volume to what it was before the fade */
295 sound_set(SOUND_VOLUME, global_settings.volume);
300 #ifdef WPS_KEYLOCK
301 static void display_keylock_text(bool locked)
303 char* s;
304 lcd_stop_scroll();
305 #ifdef HAVE_LCD_CHARCELLS
306 if(locked)
307 s = str(LANG_KEYLOCK_ON_PLAYER);
308 else
309 s = str(LANG_KEYLOCK_OFF_PLAYER);
310 #else
311 if(locked)
312 s = str(LANG_KEYLOCK_ON_RECORDER);
313 else
314 s = str(LANG_KEYLOCK_OFF_RECORDER);
315 #endif
316 splash(HZ, true, s);
319 static void waitfor_nokey(void)
321 /* wait until all keys are released */
322 while (button_get(false) != BUTTON_NONE)
323 yield();
325 #endif
327 /* demonstrates showing different formats from playtune */
328 long wps_show(void)
330 long button = 0, lastbutton = 0;
331 bool ignore_keyup = true;
332 bool restore = false;
333 long restoretimer = 0; /* timer to delay screen redraw temporarily */
334 bool exit = false;
335 bool update_track = false;
337 id3 = nid3 = NULL;
338 current_track_path[0] = '\0';
340 #ifdef HAVE_LCD_CHARCELLS
341 status_set_audio(true);
342 status_set_param(false);
343 #else
344 if(global_settings.statusbar)
345 lcd_setmargins(0, STATUSBAR_HEIGHT);
346 else
347 lcd_setmargins(0, 0);
348 #endif
350 ff_rewind = false;
352 if(audio_status() & AUDIO_STATUS_PLAY)
354 id3 = audio_current_track();
355 nid3 = audio_next_track();
356 if (id3) {
357 if (wps_display(id3, nid3))
358 return 0;
359 wps_refresh(id3, nid3, 0, WPS_REFRESH_ALL);
361 memcpy(current_track_path, id3->path, sizeof(current_track_path));
364 restore = true;
367 while ( 1 )
369 bool audio_paused = (audio_status() & AUDIO_STATUS_PAUSE)?true:false;
371 /* did someone else (i.e power thread) change audio pause mode? */
372 if (paused != audio_paused) {
373 paused = audio_paused;
375 /* if another thread paused audio, we are probably in car mode,
376 about to shut down. lets save the settings. */
377 if (paused) {
378 settings_save();
379 #if !defined(HAVE_RTC) && !defined(HAVE_SW_POWEROFF)
380 ata_flush();
381 #endif
385 #ifdef HAVE_LCD_BITMAP
386 /* when the peak meter is enabled we want to have a
387 few extra updates to make it look smooth. On the
388 other hand we don't want to waste energy if it
389 isn't displayed */
390 if (peak_meter_enabled) {
391 int i;
393 /* In high performance mode we read out the mas as
394 often as we can. There is no sleep for cpu */
395 if (global_settings.peak_meter_performance) {
396 long next_refresh = current_tick;
397 long next_big_refresh = current_tick + HZ / 5;
398 button = BUTTON_NONE;
399 while (!TIME_AFTER(current_tick, next_big_refresh)) {
400 button = button_get(false);
401 if (button != BUTTON_NONE) {
402 break;
404 peak_meter_peek();
405 sleep(1);
407 if (TIME_AFTER(current_tick, next_refresh)) {
408 wps_refresh(id3, nid3, 0, WPS_REFRESH_PEAK_METER);
409 next_refresh = current_tick + HZ / peak_meter_fps;
414 /* In energy saver mode the cpu may sleep a
415 little bit while waiting for buttons */
416 else {
417 for (i = 0; i < 4; i++) {
418 button = button_get_w_tmo(HZ / peak_meter_fps);
419 if (button != 0) {
420 break;
422 wps_refresh(id3, nid3, 0, WPS_REFRESH_PEAK_METER);
427 /* The peak meter is disabled
428 -> no additional screen updates needed */
429 else {
430 button = button_get_w_tmo(HZ/5);
432 #else
433 button = button_get_w_tmo(HZ/5);
434 #endif
436 /* discard first event if it's a button release */
437 if (button && ignore_keyup)
439 ignore_keyup = false;
440 /* Negative events are system events */
441 if (button >= 0 && button & BUTTON_REL )
442 continue;
445 #ifdef WPS_KEYLOCK
446 /* ignore non-remote buttons when keys are locked */
447 if (keys_locked &&
448 ! ((button < 0) ||
449 (button == BUTTON_NONE) ||
450 ((button & WPS_KEYLOCK) == WPS_KEYLOCK) ||
451 (button & BUTTON_REMOTE)
454 if (!(button & BUTTON_REL))
455 display_keylock_text(true);
456 restore = true;
457 button = BUTTON_NONE;
459 #endif
461 /* Exit if audio has stopped playing. This can happen if using the
462 sleep timer with the charger plugged or if starting a recording
463 from F1 */
464 if (!audio_status())
465 exit = true;
467 switch(button)
469 #ifdef WPS_CONTEXT
470 case WPS_CONTEXT:
471 onplay(id3->path, TREE_ATTR_MPA, CONTEXT_WPS);
472 restore = true;
473 break;
474 #endif
476 #ifdef WPS_RC_BROWSE
477 case WPS_RC_BROWSE:
478 #endif
479 case WPS_BROWSE:
480 #ifdef WPS_BROWSE_PRE
481 if ((lastbutton != WPS_BROWSE_PRE)
482 #ifdef WPS_RC_BROWSE_PRE
483 && (lastbutton != WPS_RC_BROWSE_PRE)
484 #endif
486 break;
487 #endif
488 #ifdef HAVE_LCD_CHARCELLS
489 status_set_record(false);
490 status_set_audio(false);
491 #endif
492 lcd_stop_scroll();
494 /* set dir browser to current playing song */
495 if (global_settings.browse_current &&
496 current_track_path[0] != '\0')
497 set_current_file(current_track_path);
499 return 0;
500 break;
502 /* play/pause */
503 case WPS_PAUSE:
504 #ifdef WPS_PAUSE_PRE
505 if (lastbutton != WPS_PAUSE_PRE)
506 break;
507 #endif
508 #ifdef WPS_RC_PAUSE
509 case WPS_RC_PAUSE:
510 #ifdef WPS_RC_PAUSE_PRE
511 if ((button == WPS_RC_PAUSE) && (lastbutton != WPS_RC_PAUSE_PRE))
512 break;
513 #endif
514 #endif
515 if ( paused )
517 paused = false;
518 if ( global_settings.fade_on_stop )
519 fade(1);
520 else
521 audio_resume();
523 else
525 paused = true;
526 if ( global_settings.fade_on_stop )
527 fade(0);
528 else
529 audio_pause();
530 settings_save();
531 #if !defined(HAVE_RTC) && !defined(HAVE_SW_POWEROFF)
532 ata_flush(); /* make sure resume info is saved */
533 #endif
535 break;
537 /* volume up */
538 case WPS_INCVOL:
539 case WPS_INCVOL | BUTTON_REPEAT:
540 #ifdef WPS_RC_INCVOL
541 case WPS_RC_INCVOL:
542 case WPS_RC_INCVOL | BUTTON_REPEAT:
543 #endif
544 global_settings.volume++;
545 if (setvol()) {
546 restore = true;
547 restoretimer = current_tick + HZ;
549 break;
551 /* volume down */
552 case WPS_DECVOL:
553 case WPS_DECVOL | BUTTON_REPEAT:
554 #ifdef WPS_RC_DECVOL
555 case WPS_RC_DECVOL:
556 case WPS_RC_DECVOL | BUTTON_REPEAT:
557 #endif
558 global_settings.volume--;
559 if (setvol()) {
560 restore = true;
561 restoretimer = current_tick + HZ;
563 break;
565 /* fast forward / rewind */
566 case WPS_FFWD:
567 case WPS_REW:
568 #ifdef WPS_RC_FFWD
569 case WPS_RC_FFWD:
570 case WPS_RC_REW:
571 #endif
572 ffwd_rew(button);
573 break;
575 /* prev / restart */
576 case WPS_PREV:
577 #ifdef WPS_PREV_PRE
578 if (lastbutton != WPS_PREV_PRE)
579 break;
580 #endif
581 #ifdef WPS_RC_PREV
582 case WPS_RC_PREV:
583 #ifdef WPS_RC_PREV_PRE
584 if ((button == WPS_RC_PREV) && (lastbutton != WPS_RC_PREV_PRE))
585 break;
586 #endif
587 #endif
588 if (!id3 || (id3->elapsed < 3*1000)) {
589 audio_prev();
591 else {
592 if (!paused)
593 audio_pause();
595 audio_ff_rewind(0);
597 if (!paused)
598 audio_resume();
600 break;
602 /* next */
603 case WPS_NEXT:
604 #ifdef WPS_NEXT_PRE
605 if (lastbutton != WPS_NEXT_PRE)
606 break;
607 #endif
608 #ifdef WPS_RC_NEXT
609 case WPS_RC_NEXT:
610 #ifdef WPS_RC_NEXT_PRE
611 if ((button == WPS_RC_NEXT) && (lastbutton != WPS_RC_NEXT_PRE))
612 break;
613 #endif
614 #endif
615 audio_next();
616 break;
618 #ifdef WPS_MENU
619 /* menu key functions */
620 case WPS_MENU:
621 #ifdef WPS_MENU_PRE
622 if (lastbutton != WPS_MENU_PRE)
623 break;
624 #endif
625 #ifdef WPS_RC_MENU
626 case WPS_RC_MENU:
627 #ifdef WPS_RC_MENU_PRE
628 if ((button == WPS_RC_MENU) && (lastbutton != WPS_RC_MENU_PRE))
629 break;
630 #endif
631 #endif
632 lcd_stop_scroll();
634 if (main_menu())
635 return true;
636 #ifdef HAVE_LCD_BITMAP
637 if (global_settings.statusbar)
638 lcd_setmargins(0, STATUSBAR_HEIGHT);
639 else
640 lcd_setmargins(0, 0);
641 #endif
642 restore = true;
643 break;
644 #endif /* WPS_MENU */
646 #ifdef WPS_KEYLOCK
647 /* key lock */
648 case WPS_KEYLOCK:
649 case WPS_KEYLOCK | BUTTON_REPEAT:
650 keys_locked = !keys_locked;
651 display_keylock_text(keys_locked);
652 restore = true;
653 waitfor_nokey();
654 break;
655 #endif
657 #if (CONFIG_KEYPAD == RECORDER_PAD) || (CONFIG_KEYPAD == IRIVER_H100_PAD)
658 /* play settings */
659 case WPS_QUICK:
660 if (quick_screen(CONTEXT_WPS, WPS_QUICK))
661 return SYS_USB_CONNECTED;
662 restore = true;
663 lastbutton = 0;
664 break;
666 /* screen settings */
667 #ifdef BUTTON_F3
668 case BUTTON_F3:
669 if (quick_screen(CONTEXT_WPS, BUTTON_F3))
670 return SYS_USB_CONNECTED;
671 restore = true;
672 break;
673 #endif
675 /* pitch screen */
676 #if CONFIG_KEYPAD == RECORDER_PAD
677 case BUTTON_ON | BUTTON_REPEAT:
678 if (2 == pitch_screen())
679 return SYS_USB_CONNECTED;
680 restore = true;
681 break;
682 #endif
683 #endif
685 /* stop and exit wps */
686 #ifdef WPS_EXIT
687 case WPS_EXIT:
688 #ifdef WPS_RC_EXIT
689 case WPS_RC_EXIT:
690 #endif
691 exit = true;
692 break;
693 #endif
695 #ifdef WPS_ID3
696 case WPS_ID3:
697 browse_id3();
698 restore = true;
699 break;
700 #endif
702 case BUTTON_NONE: /* Timeout */
703 update_track = true;
704 break;
706 default:
707 if(default_event_handler(button) == SYS_USB_CONNECTED)
708 return SYS_USB_CONNECTED;
709 break;
712 if (update_track)
714 if (update())
716 /* set dir browser to current playing song */
717 if (global_settings.browse_current &&
718 current_track_path[0] != '\0')
719 set_current_file(current_track_path);
721 return 0;
723 update_track = false;
726 if (exit) {
727 #ifdef HAVE_LCD_CHARCELLS
728 status_set_record(false);
729 status_set_audio(false);
730 #endif
731 if (global_settings.fade_on_stop)
732 fade(0);
734 lcd_stop_scroll();
735 bookmark_autobookmark();
736 audio_stop();
738 /* Keys can be locked when exiting, so either unlock here
739 or implement key locking in tree.c too */
740 keys_locked=false;
742 /* set dir browser to current playing song */
743 if (global_settings.browse_current &&
744 current_track_path[0] != '\0')
745 set_current_file(current_track_path);
747 return 0;
750 if ( button )
751 ata_spin();
753 if (restore &&
754 ((restoretimer == 0) ||
755 (restoretimer < current_tick)))
757 restore = false;
758 restoretimer = 0;
759 if (wps_display(id3, nid3))
761 /* set dir browser to current playing song */
762 if (global_settings.browse_current &&
763 current_track_path[0] != '\0')
764 set_current_file(current_track_path);
766 return 0;
769 if (id3)
770 wps_refresh(id3, nid3, 0, WPS_REFRESH_NON_STATIC);
772 if (button != BUTTON_NONE)
773 lastbutton = button;
775 return 0; /* unreachable - just to reduce compiler warnings */