Fix hopping backwards if skip length is > 0 and we're near the end of the track ...
[kugel-rb/myfork.git] / apps / gui / gwps.c
blob8256fee84f8d50409a2b656992fb00ee002ef2a3
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Jerome Kuptz
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include <stdio.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include "config.h"
26 #include "system.h"
27 #include "file.h"
28 #include "lcd.h"
29 #include "font.h"
30 #include "backlight.h"
31 #include "action.h"
32 #include "kernel.h"
33 #include "filetypes.h"
34 #include "debug.h"
35 #include "sprintf.h"
36 #include "settings.h"
37 #include "gwps.h"
38 #include "gwps-common.h"
39 #include "audio.h"
40 #include "usb.h"
41 #include "status.h"
42 #include "storage.h"
43 #include "screens.h"
44 #include "playlist.h"
45 #ifdef HAVE_LCD_BITMAP
46 #include "icons.h"
47 #include "peakmeter.h"
48 #endif
49 #include "action.h"
50 #include "lang.h"
51 #include "bookmark.h"
52 #include "misc.h"
53 #include "sound.h"
54 #include "onplay.h"
55 #include "abrepeat.h"
56 #include "playback.h"
57 #include "splash.h"
58 #include "cuesheet.h"
59 #include "ata_idle_notify.h"
60 #include "root_menu.h"
61 #include "backdrop.h"
62 #include "quickscreen.h"
63 #include "pitchscreen.h"
64 #include "appevents.h"
65 #include "viewport.h"
66 #include "pcmbuf.h"
68 #define RESTORE_WPS_INSTANTLY 0l
69 #define RESTORE_WPS_NEXT_SECOND ((long)(HZ+current_tick))
70 /* in milliseconds */
71 #define DEFAULT_SKIP_TRESH 3000ul
73 static int wpsbars;
74 /* currently only one wps_state is needed */
75 struct wps_state wps_state;
76 struct gui_wps gui_wps[NB_SCREENS];
77 static struct wps_data wps_datas[NB_SCREENS];
79 /* initial setup of wps_data */
80 static void wps_state_init(void);
82 static void change_dir(int direction)
84 if (global_settings.prevent_skip)
85 return;
87 if (direction < 0)
88 audio_prev_dir();
89 else if (direction > 0)
90 audio_next_dir();
93 static void prev_track(unsigned long skip_thresh)
95 if (!global_settings.prevent_skip
96 && (wps_state.id3->elapsed < skip_thresh))
98 audio_prev();
99 return;
101 else
103 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
105 curr_cuesheet_skip(-1, wps_state.id3->elapsed);
106 return;
109 if (!wps_state.paused)
110 #if (CONFIG_CODEC == SWCODEC)
111 audio_pre_ff_rewind();
112 #else
113 audio_pause();
114 #endif
116 audio_ff_rewind(0);
118 #if (CONFIG_CODEC != SWCODEC)
119 if (!wps_state.paused)
120 audio_resume();
121 #endif
125 static void next_track(void)
127 if (global_settings.prevent_skip)
128 return;
129 /* take care of if we're playing a cuesheet */
130 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
132 if (curr_cuesheet_skip(1, wps_state.id3->elapsed))
134 /* if the result was false, then we really want
135 to skip to the next track */
136 return;
140 audio_next();
143 static void play_hop(int direction)
145 unsigned long step = ((unsigned long)global_settings.skip_length)*1000;
146 unsigned long elapsed = wps_state.id3->elapsed;
147 unsigned long remaining = wps_state.id3->length - elapsed;
149 if (!global_settings.prevent_skip &&
150 (!step ||
151 (direction > 0 && step >= remaining) ||
152 (direction < 0 && elapsed < DEFAULT_SKIP_TRESH)))
153 { /* Do normal track skipping */
154 if (direction > 0)
155 next_track();
156 else if (direction < 0)
157 prev_track(DEFAULT_SKIP_TRESH);
158 return;
161 if (direction == 1 && step >= remaining)
163 #if CONFIG_CODEC == SWCODEC
164 if(global_settings.beep)
165 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
166 #endif
167 return;
169 else if ((direction == -1 && elapsed < step))
171 elapsed = 0;
173 else
175 elapsed += step * direction;
177 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused)
179 #if (CONFIG_CODEC == SWCODEC)
180 audio_pre_ff_rewind();
181 #else
182 audio_pause();
183 #endif
185 audio_ff_rewind(wps_state.id3->elapsed = elapsed);
186 #if (CONFIG_CODEC != SWCODEC)
187 if (!wps_state.paused)
188 audio_resume();
189 #endif
192 static void gwps_fix_statusbars(void)
194 #ifdef HAVE_LCD_BITMAP
195 int i;
196 wpsbars = VP_SB_HIDE_ALL;
197 FOR_NB_SCREENS(i)
199 bool draw = false;
200 if (gui_wps[i].data->wps_sb_tag)
201 draw = gui_wps[i].data->show_sb_on_wps;
202 else if (global_settings.statusbar)
203 wpsbars |= VP_SB_ONSCREEN(i);
204 if (draw)
205 wpsbars |= (VP_SB_ONSCREEN(i) | VP_SB_IGNORE_SETTING(i));
207 #else
208 wpsbars = VP_SB_ALLSCREENS;
209 #endif
213 static void gwps_leave_wps(void)
215 int i, oldbars = VP_SB_HIDE_ALL;
217 FOR_NB_SCREENS(i)
218 gui_wps[i].display->stop_scroll();
219 if (global_settings.statusbar)
220 oldbars = VP_SB_ALLSCREENS;
222 #if LCD_DEPTH > 1
223 show_main_backdrop();
224 #endif
225 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
226 show_remote_main_backdrop();
227 #endif
228 viewportmanager_set_statusbar(oldbars);
231 void gwps_draw_statusbars(void)
233 viewportmanager_set_statusbar(wpsbars);
236 /* The WPS can be left in two ways:
237 * a) call a function, which draws over the wps. In this case, the wps
238 * will be still active (i.e. the below function didn't return)
239 * b) return with a value evaluated by root_menu.c, in this case the wps
240 * is really left, and root_menu will handle the next screen
242 * In either way, call gwps_leave_wps(), in order to restore the correct
243 * "main screen" backdrops and statusbars
245 long gui_wps_show(void)
247 long button = 0;
248 bool restore = false;
249 long restoretimer = RESTORE_WPS_INSTANTLY; /* timer to delay screen redraw temporarily */
250 bool exit = false;
251 bool bookmark = false;
252 bool update_track = false;
253 int i;
254 long last_left = 0, last_right = 0;
255 wps_state_init();
257 #ifdef HAVE_LCD_CHARCELLS
258 status_set_audio(true);
259 status_set_param(false);
260 #endif
262 gwps_fix_statusbars();
264 #ifdef AB_REPEAT_ENABLE
265 ab_repeat_init();
266 ab_reset_markers();
267 #endif
268 if(audio_status() & AUDIO_STATUS_PLAY)
270 wps_state.id3 = audio_current_track();
271 wps_state.nid3 = audio_next_track();
272 restore = true; /* force initial full redraw */
275 while ( 1 )
277 bool audio_paused = (audio_status() & AUDIO_STATUS_PAUSE)?true:false;
279 /* did someone else (i.e power thread) change audio pause mode? */
280 if (wps_state.paused != audio_paused) {
281 wps_state.paused = audio_paused;
283 /* if another thread paused audio, we are probably in car mode,
284 about to shut down. lets save the settings. */
285 if (wps_state.paused) {
286 settings_save();
287 #if !defined(HAVE_RTC_RAM) && !defined(HAVE_SW_POWEROFF)
288 call_storage_idle_notifys(true);
289 #endif
293 #ifdef HAVE_LCD_BITMAP
294 /* when the peak meter is enabled we want to have a
295 few extra updates to make it look smooth. On the
296 other hand we don't want to waste energy if it
297 isn't displayed */
298 bool pm=false;
299 FOR_NB_SCREENS(i)
301 if(gui_wps[i].data->peak_meter_enabled)
302 pm = true;
305 if (pm) {
306 long next_refresh = current_tick;
307 long next_big_refresh = current_tick + HZ / 5;
308 button = BUTTON_NONE;
309 while (TIME_BEFORE(current_tick, next_big_refresh)) {
310 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_NOBLOCK);
311 if (button != ACTION_NONE) {
312 break;
314 peak_meter_peek();
315 sleep(0); /* Sleep until end of current tick. */
317 if (TIME_AFTER(current_tick, next_refresh)) {
318 FOR_NB_SCREENS(i)
320 if(gui_wps[i].data->peak_meter_enabled)
321 gui_wps_redraw(&gui_wps[i], 0,
322 WPS_REFRESH_PEAK_METER);
323 next_refresh += HZ / PEAK_METER_FPS;
330 /* The peak meter is disabled
331 -> no additional screen updates needed */
332 else
333 #endif
335 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,HZ/5);
338 /* Exit if audio has stopped playing. This happens e.g. at end of
339 playlist or if using the sleep timer. */
340 if (!(audio_status() & AUDIO_STATUS_PLAY))
341 exit = true;
342 /* The iPods/X5/M5 use a single button for the A-B mode markers,
343 defined as ACTION_WPSAB_SINGLE in their config files. */
344 #ifdef ACTION_WPSAB_SINGLE
345 if (!global_settings.party_mode && ab_repeat_mode_enabled())
347 static int wps_ab_state = 0;
348 if (button == ACTION_WPSAB_SINGLE)
350 switch (wps_ab_state)
352 case 0: /* set the A spot */
353 button = ACTION_WPS_ABSETA_PREVDIR;
354 break;
355 case 1: /* set the B spot */
356 button = ACTION_WPS_ABSETB_NEXTDIR;
357 break;
358 case 2:
359 button = ACTION_WPS_ABRESET;
360 break;
362 wps_ab_state = (wps_ab_state+1) % 3;
365 #endif
366 switch(button)
368 case ACTION_WPS_CONTEXT:
370 gwps_leave_wps();
371 /* if music is stopped in the context menu we want to exit the wps */
372 if (onplay(wps_state.id3->path,
373 FILE_ATTR_AUDIO, CONTEXT_WPS) == ONPLAY_MAINMENU
374 || !audio_status())
375 return GO_TO_ROOT;
376 /* track might have changed */
377 update_track = true;
378 restore = true;
380 break;
382 case ACTION_WPS_BROWSE:
383 #ifdef HAVE_LCD_CHARCELLS
384 status_set_record(false);
385 status_set_audio(false);
386 #endif
387 gwps_leave_wps();
388 return GO_TO_PREVIOUS_BROWSER;
389 break;
391 /* play/pause */
392 case ACTION_WPS_PLAY:
393 if (global_settings.party_mode)
394 break;
395 if ( wps_state.paused )
397 wps_state.paused = false;
398 if ( global_settings.fade_on_stop )
399 fade(true, true);
400 else
401 audio_resume();
403 else
405 wps_state.paused = true;
406 if ( global_settings.fade_on_stop )
407 fade(false, true);
408 else
409 audio_pause();
410 settings_save();
411 #if !defined(HAVE_RTC_RAM) && !defined(HAVE_SW_POWEROFF)
412 call_storage_idle_notifys(true); /* make sure resume info is saved */
413 #endif
415 break;
417 case ACTION_WPS_VOLUP:
419 FOR_NB_SCREENS(i)
420 gui_wps[i].data->button_time_volume = current_tick;
421 global_settings.volume++;
422 bool res = false;
423 setvol();
424 FOR_NB_SCREENS(i)
426 if(update_onvol_change(&gui_wps[i]))
427 res = true;
429 if (res) {
430 restore = true;
431 restoretimer = RESTORE_WPS_NEXT_SECOND;
434 break;
435 case ACTION_WPS_VOLDOWN:
437 FOR_NB_SCREENS(i)
438 gui_wps[i].data->button_time_volume = current_tick;
439 global_settings.volume--;
440 setvol();
441 bool res = false;
442 FOR_NB_SCREENS(i)
444 if(update_onvol_change(&gui_wps[i]))
445 res = true;
447 if (res) {
448 restore = true;
449 restoretimer = RESTORE_WPS_NEXT_SECOND;
452 break;
453 /* fast forward
454 OR next dir if this is straight after ACTION_WPS_SKIPNEXT */
455 case ACTION_WPS_SEEKFWD:
456 if (global_settings.party_mode)
457 break;
458 if (current_tick -last_right < HZ)
460 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
462 audio_next();
464 else
466 change_dir(1);
469 else
470 ffwd_rew(ACTION_WPS_SEEKFWD);
471 last_right = last_left = 0;
472 break;
473 /* fast rewind
474 OR prev dir if this is straight after ACTION_WPS_SKIPPREV,*/
475 case ACTION_WPS_SEEKBACK:
476 if (global_settings.party_mode)
477 break;
478 if (current_tick -last_left < HZ)
480 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
482 if (!wps_state.paused)
483 #if (CONFIG_CODEC == SWCODEC)
484 audio_pre_ff_rewind();
485 #else
486 audio_pause();
487 #endif
488 audio_ff_rewind(0);
490 else
492 change_dir(-1);
495 else
496 ffwd_rew(ACTION_WPS_SEEKBACK);
497 last_left = last_right = 0;
498 break;
500 /* prev / restart */
501 case ACTION_WPS_SKIPPREV:
502 if (global_settings.party_mode)
503 break;
504 last_left = current_tick;
505 update_track = true;
506 #ifdef AB_REPEAT_ENABLE
507 /* if we're in A/B repeat mode and the current position
508 is past the A marker, jump back to the A marker... */
509 if ( ab_repeat_mode_enabled() )
511 if ( ab_after_A_marker(wps_state.id3->elapsed) )
513 ab_jump_to_A_marker();
514 break;
515 #if (AB_REPEAT_ENABLE == 2)
516 } else {
517 ab_reset_markers();
518 #endif
521 else
522 /* ...otherwise, do it normally */
523 #endif
524 play_hop(-1);
525 break;
527 /* next
528 OR if skip length set, hop by predetermined amount. */
529 case ACTION_WPS_SKIPNEXT:
530 if (global_settings.party_mode)
531 break;
532 last_right = current_tick;
533 update_track = true;
534 #ifdef AB_REPEAT_ENABLE
535 /* if we're in A/B repeat mode and the current position is
536 before the A marker, jump to the A marker... */
537 if ( ab_repeat_mode_enabled() )
539 if ( ab_before_A_marker(wps_state.id3->elapsed) )
541 ab_jump_to_A_marker();
542 break;
543 #if (AB_REPEAT_ENABLE == 2)
544 } else {
545 ab_reset_markers();
546 #endif
549 else
550 /* ...otherwise, do it normally */
551 #endif
552 play_hop(1);
553 break;
554 /* next / prev directories */
555 /* and set A-B markers if in a-b mode */
556 case ACTION_WPS_ABSETB_NEXTDIR:
557 if (global_settings.party_mode)
558 break;
559 #if defined(AB_REPEAT_ENABLE)
560 if (ab_repeat_mode_enabled())
562 ab_set_B_marker(wps_state.id3->elapsed);
563 ab_jump_to_A_marker();
564 update_track = true;
566 else
567 #endif
569 change_dir(1);
571 break;
572 case ACTION_WPS_ABSETA_PREVDIR:
573 if (global_settings.party_mode)
574 break;
575 #if defined(AB_REPEAT_ENABLE)
576 if (ab_repeat_mode_enabled())
577 ab_set_A_marker(wps_state.id3->elapsed);
578 else
579 #endif
581 change_dir(-1);
583 break;
584 /* menu key functions */
585 case ACTION_WPS_MENU:
586 gwps_leave_wps();
587 return GO_TO_ROOT;
588 break;
591 #ifdef HAVE_QUICKSCREEN
592 case ACTION_WPS_QUICKSCREEN:
594 gwps_leave_wps();
595 if (quick_screen_quick(button))
596 return SYS_USB_CONNECTED;
597 restore = true;
599 break;
600 #endif /* HAVE_QUICKSCREEN */
602 /* screen settings */
603 #ifdef BUTTON_F3
604 case ACTION_F3:
606 gwps_leave_wps();
607 if (quick_screen_f3(BUTTON_F3))
608 return SYS_USB_CONNECTED;
609 restore = true;
611 break;
612 #endif /* BUTTON_F3 */
614 /* pitch screen */
615 #ifdef HAVE_PITCHSCREEN
616 case ACTION_WPS_PITCHSCREEN:
618 gwps_leave_wps();
619 if (1 == gui_syncpitchscreen_run())
620 return SYS_USB_CONNECTED;
621 restore = true;
623 break;
624 #endif /* HAVE_PITCHSCREEN */
626 #ifdef AB_REPEAT_ENABLE
627 /* reset A&B markers */
628 case ACTION_WPS_ABRESET:
629 if (ab_repeat_mode_enabled())
631 ab_reset_markers();
632 update_track = true;
634 break;
635 #endif /* AB_REPEAT_ENABLE */
637 /* stop and exit wps */
638 case ACTION_WPS_STOP:
639 if (global_settings.party_mode)
640 break;
641 bookmark = true;
642 exit = true;
643 break;
645 case ACTION_WPS_ID3SCREEN:
647 gwps_leave_wps();
648 browse_id3();
649 restore = true;
651 break;
653 case ACTION_REDRAW: /* yes are locked, just redraw */
654 restore = true;
655 break;
656 case ACTION_NONE: /* Timeout */
657 update_track = true;
658 ffwd_rew(button); /* hopefully fix the ffw/rwd bug */
659 break;
660 #ifdef HAVE_RECORDING
661 case ACTION_WPS_REC:
662 exit = true;
663 break;
664 #endif
665 case SYS_POWEROFF:
666 gwps_leave_wps();
667 default_event_handler(SYS_POWEROFF);
668 break;
670 default:
671 if(default_event_handler(button) == SYS_USB_CONNECTED)
672 return GO_TO_ROOT;
673 update_track = true;
674 break;
677 if (update_track)
679 FOR_NB_SCREENS(i)
681 gui_wps_update(&gui_wps[i]);
683 update_track = false;
686 if (restore && wps_state.id3 &&
687 ((restoretimer == RESTORE_WPS_INSTANTLY) ||
688 TIME_AFTER(current_tick, restoretimer)))
690 restore = false;
691 restoretimer = RESTORE_WPS_INSTANTLY;
692 FOR_NB_SCREENS(i)
694 screens[i].stop_scroll();
695 gui_wps_display(&gui_wps[i]);
699 if (exit) {
700 #ifdef HAVE_LCD_CHARCELLS
701 status_set_record(false);
702 status_set_audio(false);
703 #endif
704 if (global_settings.fade_on_stop)
705 fade(false, true);
707 if (bookmark)
708 bookmark_autobookmark();
709 audio_stop();
710 #ifdef AB_REPEAT_ENABLE
711 ab_reset_markers();
712 #endif
713 gwps_leave_wps();
714 #ifdef HAVE_RECORDING
715 if (button == ACTION_WPS_REC)
716 return GO_TO_RECSCREEN;
717 #endif
718 if (global_settings.browse_current)
719 return GO_TO_PREVIOUS_BROWSER;
720 return GO_TO_PREVIOUS;
723 if (button && !IS_SYSEVENT(button) )
724 storage_spin();
726 return GO_TO_ROOT; /* unreachable - just to reduce compiler warnings */
729 /* needs checking if needed end*/
731 /* wps_state */
733 static void wps_state_init(void)
735 wps_state.ff_rewind = false;
736 wps_state.paused = false;
737 wps_state.id3 = NULL;
738 wps_state.nid3 = NULL;
741 /* wps_state end*/
743 #ifdef HAVE_LCD_BITMAP
744 static void statusbar_toggle_handler(void *data)
746 (void)data;
747 int i;
748 gwps_fix_statusbars();
750 FOR_NB_SCREENS(i)
752 struct viewport *vp = &gui_wps[i].data->viewports[0].vp;
753 bool draw = wpsbars & (VP_SB_ONSCREEN(i) | VP_SB_IGNORE_SETTING(i));
754 if (!draw)
756 vp->y = 0;
757 vp->height = screens[i].lcdheight;
759 else
761 vp->y = STATUSBAR_HEIGHT;
762 vp->height = screens[i].lcdheight - STATUSBAR_HEIGHT;
766 #endif
768 void gui_sync_wps_init(void)
770 int i;
771 FOR_NB_SCREENS(i)
773 wps_data_init(&wps_datas[i]);
774 #ifdef HAVE_ALBUMART
775 wps_datas[i].wps_uses_albumart = 0;
776 #endif
777 #ifdef HAVE_REMOTE_LCD
778 wps_datas[i].remote_wps = (i != 0);
779 #endif
780 gui_wps[i].data = &wps_datas[i];
781 gui_wps[i].display = &screens[i];
782 /* Currently no seperate wps_state needed/possible
783 so use the only aviable ( "global" ) one */
784 gui_wps[i].state = &wps_state;
786 #ifdef HAVE_LCD_BITMAP
787 add_event(GUI_EVENT_STATUSBAR_TOGGLE, false, statusbar_toggle_handler);
788 #endif
789 #if LCD_DEPTH > 1
790 unload_wps_backdrop();
791 #endif
792 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
793 unload_remote_wps_backdrop();
794 #endif
797 #ifdef HAVE_ALBUMART
798 /* Returns true if at least one of the gui_wps screens has an album art
799 tag in its wps structure */
800 bool gui_sync_wps_uses_albumart(void)
802 int i;
803 FOR_NB_SCREENS(i) {
804 struct gui_wps *gwps = &gui_wps[i];
805 if (gwps->data && (gwps->data->wps_uses_albumart != WPS_ALBUMART_NONE))
806 return true;
808 return false;
810 #endif