FS9795 - some playback cleanup.
[kugel-rb.git] / apps / gui / gwps.c
blob5474b302f066ef26cc7e91c390770d8002fbb840
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);
81 static void track_changed_callback(void *param);
82 static void nextid3available_callback(void* param);
84 static void change_dir(int direction)
86 if (global_settings.prevent_skip)
87 return;
89 if (direction < 0)
90 audio_prev_dir();
91 else if (direction > 0)
92 audio_next_dir();
95 static void prev_track(unsigned long skip_thresh)
97 if (wps_state.id3->elapsed < skip_thresh)
99 audio_prev();
100 return;
102 else
104 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
106 curr_cuesheet_skip(-1, wps_state.id3->elapsed);
107 return;
110 if (!wps_state.paused)
111 #if (CONFIG_CODEC == SWCODEC)
112 audio_pre_ff_rewind();
113 #else
114 audio_pause();
115 #endif
117 audio_ff_rewind(0);
119 #if (CONFIG_CODEC != SWCODEC)
120 if (!wps_state.paused)
121 audio_resume();
122 #endif
126 static void next_track(void)
128 /* take care of if we're playing a cuesheet */
129 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
131 if (curr_cuesheet_skip(1, wps_state.id3->elapsed))
133 /* if the result was false, then we really want
134 to skip to the next track */
135 return;
139 audio_next();
142 static void play_hop(int direction)
144 unsigned long step = ((unsigned long)global_settings.skip_length)*1000;
145 unsigned long elapsed = wps_state.id3->elapsed;
146 unsigned long remaining = wps_state.id3->length - elapsed;
148 if (!global_settings.prevent_skip &&
149 (!step ||
150 (direction > 0 && step >= remaining) ||
151 (direction < 0 && elapsed < DEFAULT_SKIP_TRESH)))
152 { /* Do normal track skipping */
153 if (direction > 0)
154 next_track();
155 else if (direction < 0)
156 prev_track(DEFAULT_SKIP_TRESH);
157 return;
160 if (direction == 1 && step >= remaining)
162 #if CONFIG_CODEC == SWCODEC
163 if(global_settings.beep)
164 pcmbuf_beep(1000, 150, 1500*global_settings.beep);
165 #endif
166 return;
168 else if ((direction == -1 && elapsed < step))
170 elapsed = 0;
172 else
174 elapsed += step * direction;
176 if((audio_status() & AUDIO_STATUS_PLAY) && !wps_state.paused)
178 #if (CONFIG_CODEC == SWCODEC)
179 audio_pre_ff_rewind();
180 #else
181 audio_pause();
182 #endif
184 audio_ff_rewind(wps_state.id3->elapsed = elapsed);
185 #if (CONFIG_CODEC != SWCODEC)
186 if (!wps_state.paused)
187 audio_resume();
188 #endif
191 static void gwps_fix_statusbars(void)
193 #ifdef HAVE_LCD_BITMAP
194 int i;
195 wpsbars = VP_SB_HIDE_ALL;
196 FOR_NB_SCREENS(i)
198 bool draw = false;
199 if (gui_wps[i].data->wps_sb_tag)
200 draw = gui_wps[i].data->show_sb_on_wps;
201 else if (global_settings.statusbar)
202 wpsbars |= VP_SB_ONSCREEN(i);
203 if (draw)
204 wpsbars |= (VP_SB_ONSCREEN(i) | VP_SB_IGNORE_SETTING(i));
206 #else
207 wpsbars = VP_SB_ALLSCREENS;
208 #endif
212 static void gwps_leave_wps(void)
214 int i, oldbars = VP_SB_HIDE_ALL;
216 FOR_NB_SCREENS(i)
217 gui_wps[i].display->stop_scroll();
218 if (global_settings.statusbar)
219 oldbars = VP_SB_ALLSCREENS;
221 #if LCD_DEPTH > 1
222 show_main_backdrop();
223 #endif
224 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
225 show_remote_main_backdrop();
226 #endif
227 viewportmanager_set_statusbar(oldbars);
230 void gwps_draw_statusbars(void)
232 viewportmanager_set_statusbar(wpsbars);
235 /* The WPS can be left in two ways:
236 * a) call a function, which draws over the wps. In this case, the wps
237 * will be still active (i.e. the below function didn't return)
238 * b) return with a value evaluated by root_menu.c, in this case the wps
239 * is really left, and root_menu will handle the next screen
241 * In either way, call gwps_leave_wps(), in order to restore the correct
242 * "main screen" backdrops and statusbars
244 long gui_wps_show(void)
246 long button = 0;
247 bool restore = false;
248 long restoretimer = RESTORE_WPS_INSTANTLY; /* timer to delay screen redraw temporarily */
249 bool exit = false;
250 bool bookmark = false;
251 bool update_track = false, partial_update = false;
252 int i;
253 long last_left = 0, last_right = 0;
254 wps_state_init();
256 #ifdef HAVE_LCD_CHARCELLS
257 status_set_audio(true);
258 status_set_param(false);
259 #endif
261 gwps_fix_statusbars();
263 #ifdef AB_REPEAT_ENABLE
264 ab_repeat_init();
265 ab_reset_markers();
266 #endif
267 if(audio_status() & AUDIO_STATUS_PLAY)
269 wps_state.id3 = audio_current_track();
270 wps_state.nid3 = audio_next_track();
271 restore = true; /* force initial full redraw */
274 while ( 1 )
276 bool audio_paused = (audio_status() & AUDIO_STATUS_PAUSE)?true:false;
278 /* did someone else (i.e power thread) change audio pause mode? */
279 if (wps_state.paused != audio_paused) {
280 wps_state.paused = audio_paused;
282 /* if another thread paused audio, we are probably in car mode,
283 about to shut down. lets save the settings. */
284 if (wps_state.paused) {
285 settings_save();
286 #if !defined(HAVE_RTC_RAM) && !defined(HAVE_SW_POWEROFF)
287 call_storage_idle_notifys(true);
288 #endif
292 #ifdef HAVE_LCD_BITMAP
293 /* when the peak meter is enabled we want to have a
294 few extra updates to make it look smooth. On the
295 other hand we don't want to waste energy if it
296 isn't displayed */
297 bool pm=false;
298 FOR_NB_SCREENS(i)
300 if(gui_wps[i].data->peak_meter_enabled)
301 pm = true;
304 if (pm) {
305 long next_refresh = current_tick;
306 long next_big_refresh = current_tick + HZ / 5;
307 button = BUTTON_NONE;
308 while (TIME_BEFORE(current_tick, next_big_refresh)) {
309 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,TIMEOUT_NOBLOCK);
310 if (button != ACTION_NONE) {
311 break;
313 peak_meter_peek();
314 sleep(0); /* Sleep until end of current tick. */
316 if (TIME_AFTER(current_tick, next_refresh)) {
317 FOR_NB_SCREENS(i)
319 if(gui_wps[i].data->peak_meter_enabled)
320 gui_wps_redraw(&gui_wps[i], 0,
321 WPS_REFRESH_PEAK_METER);
322 next_refresh += HZ / PEAK_METER_FPS;
329 /* The peak meter is disabled
330 -> no additional screen updates needed */
331 else
332 #endif
334 button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,HZ/5);
337 /* Exit if audio has stopped playing. This happens e.g. at end of
338 playlist or if using the sleep timer. */
339 if (!(audio_status() & AUDIO_STATUS_PLAY))
340 exit = true;
341 /* The iPods/X5/M5 use a single button for the A-B mode markers,
342 defined as ACTION_WPSAB_SINGLE in their config files. */
343 #ifdef ACTION_WPSAB_SINGLE
344 if (!global_settings.party_mode && ab_repeat_mode_enabled())
346 static int wps_ab_state = 0;
347 if (button == ACTION_WPSAB_SINGLE)
349 switch (wps_ab_state)
351 case 0: /* set the A spot */
352 button = ACTION_WPS_ABSETA_PREVDIR;
353 break;
354 case 1: /* set the B spot */
355 button = ACTION_WPS_ABSETB_NEXTDIR;
356 break;
357 case 2:
358 button = ACTION_WPS_ABRESET;
359 break;
361 wps_ab_state = (wps_ab_state+1) % 3;
364 #endif
365 switch(button)
367 case ACTION_WPS_CONTEXT:
369 gwps_leave_wps();
370 /* if music is stopped in the context menu we want to exit the wps */
371 if (onplay(wps_state.id3->path,
372 FILE_ATTR_AUDIO, CONTEXT_WPS) == ONPLAY_MAINMENU
373 || !audio_status())
374 return GO_TO_ROOT;
375 /* track might have changed */
376 update_track = true;
377 restore = true;
379 break;
381 case ACTION_WPS_BROWSE:
382 #ifdef HAVE_LCD_CHARCELLS
383 status_set_record(false);
384 status_set_audio(false);
385 #endif
386 gwps_leave_wps();
387 return GO_TO_PREVIOUS_BROWSER;
388 break;
390 /* play/pause */
391 case ACTION_WPS_PLAY:
392 if (global_settings.party_mode)
393 break;
394 if ( wps_state.paused )
396 wps_state.paused = false;
397 if ( global_settings.fade_on_stop )
398 fade(true, true);
399 else
400 audio_resume();
402 else
404 wps_state.paused = true;
405 if ( global_settings.fade_on_stop )
406 fade(false, true);
407 else
408 audio_pause();
409 settings_save();
410 #if !defined(HAVE_RTC_RAM) && !defined(HAVE_SW_POWEROFF)
411 call_storage_idle_notifys(true); /* make sure resume info is saved */
412 #endif
414 break;
416 case ACTION_WPS_VOLUP:
418 FOR_NB_SCREENS(i)
419 gui_wps[i].data->button_time_volume = current_tick;
420 global_settings.volume++;
421 bool res = false;
422 setvol();
423 FOR_NB_SCREENS(i)
425 if(update_onvol_change(&gui_wps[i]))
426 res = true;
428 if (res) {
429 restore = true;
430 restoretimer = RESTORE_WPS_NEXT_SECOND;
433 break;
434 case ACTION_WPS_VOLDOWN:
436 FOR_NB_SCREENS(i)
437 gui_wps[i].data->button_time_volume = current_tick;
438 global_settings.volume--;
439 setvol();
440 bool res = false;
441 FOR_NB_SCREENS(i)
443 if(update_onvol_change(&gui_wps[i]))
444 res = true;
446 if (res) {
447 restore = true;
448 restoretimer = RESTORE_WPS_NEXT_SECOND;
451 break;
452 /* fast forward
453 OR next dir if this is straight after ACTION_WPS_SKIPNEXT */
454 case ACTION_WPS_SEEKFWD:
455 if (global_settings.party_mode)
456 break;
457 if (current_tick -last_right < HZ)
459 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
461 audio_next();
463 else
465 change_dir(1);
468 else
469 ffwd_rew(ACTION_WPS_SEEKFWD);
470 last_right = last_left = 0;
471 break;
472 /* fast rewind
473 OR prev dir if this is straight after ACTION_WPS_SKIPPREV,*/
474 case ACTION_WPS_SEEKBACK:
475 if (global_settings.party_mode)
476 break;
477 if (current_tick -last_left < HZ)
479 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type)
481 if (!wps_state.paused)
482 #if (CONFIG_CODEC == SWCODEC)
483 audio_pre_ff_rewind();
484 #else
485 audio_pause();
486 #endif
487 audio_ff_rewind(0);
489 else
491 change_dir(-1);
494 else
495 ffwd_rew(ACTION_WPS_SEEKBACK);
496 last_left = last_right = 0;
497 break;
499 /* prev / restart */
500 case ACTION_WPS_SKIPPREV:
501 if (global_settings.party_mode)
502 break;
503 last_left = current_tick;
504 update_track = true;
505 #ifdef AB_REPEAT_ENABLE
506 /* if we're in A/B repeat mode and the current position
507 is past the A marker, jump back to the A marker... */
508 if ( ab_repeat_mode_enabled() )
510 if ( ab_after_A_marker(wps_state.id3->elapsed) )
512 ab_jump_to_A_marker();
513 break;
514 #if (AB_REPEAT_ENABLE == 2)
515 } else {
516 ab_reset_markers();
517 #endif
520 else
521 /* ...otherwise, do it normally */
522 #endif
523 play_hop(-1);
524 break;
526 /* next
527 OR if skip length set, hop by predetermined amount. */
528 case ACTION_WPS_SKIPNEXT:
529 if (global_settings.party_mode)
530 break;
531 last_right = current_tick;
532 update_track = true;
533 #ifdef AB_REPEAT_ENABLE
534 /* if we're in A/B repeat mode and the current position is
535 before the A marker, jump to the A marker... */
536 if ( ab_repeat_mode_enabled() )
538 if ( ab_before_A_marker(wps_state.id3->elapsed) )
540 ab_jump_to_A_marker();
541 break;
542 #if (AB_REPEAT_ENABLE == 2)
543 } else {
544 ab_reset_markers();
545 #endif
548 else
549 /* ...otherwise, do it normally */
550 #endif
551 play_hop(1);
552 break;
553 /* next / prev directories */
554 /* and set A-B markers if in a-b mode */
555 case ACTION_WPS_ABSETB_NEXTDIR:
556 if (global_settings.party_mode)
557 break;
558 #if defined(AB_REPEAT_ENABLE)
559 if (ab_repeat_mode_enabled())
561 ab_set_B_marker(wps_state.id3->elapsed);
562 ab_jump_to_A_marker();
563 update_track = true;
565 else
566 #endif
568 change_dir(1);
570 break;
571 case ACTION_WPS_ABSETA_PREVDIR:
572 if (global_settings.party_mode)
573 break;
574 #if defined(AB_REPEAT_ENABLE)
575 if (ab_repeat_mode_enabled())
576 ab_set_A_marker(wps_state.id3->elapsed);
577 else
578 #endif
580 change_dir(-1);
582 break;
583 /* menu key functions */
584 case ACTION_WPS_MENU:
585 gwps_leave_wps();
586 return GO_TO_ROOT;
587 break;
590 #ifdef HAVE_QUICKSCREEN
591 case ACTION_WPS_QUICKSCREEN:
593 gwps_leave_wps();
594 if (quick_screen_quick(button))
595 return SYS_USB_CONNECTED;
596 restore = true;
598 break;
599 #endif /* HAVE_QUICKSCREEN */
601 /* screen settings */
602 #ifdef BUTTON_F3
603 case ACTION_F3:
605 gwps_leave_wps();
606 if (quick_screen_f3(BUTTON_F3))
607 return SYS_USB_CONNECTED;
608 restore = true;
610 break;
611 #endif /* BUTTON_F3 */
613 /* pitch screen */
614 #ifdef HAVE_PITCHSCREEN
615 case ACTION_WPS_PITCHSCREEN:
617 gwps_leave_wps();
618 if (1 == gui_syncpitchscreen_run())
619 return SYS_USB_CONNECTED;
620 restore = true;
622 break;
623 #endif /* HAVE_PITCHSCREEN */
625 #ifdef AB_REPEAT_ENABLE
626 /* reset A&B markers */
627 case ACTION_WPS_ABRESET:
628 if (ab_repeat_mode_enabled())
630 ab_reset_markers();
631 update_track = true;
633 break;
634 #endif /* AB_REPEAT_ENABLE */
636 /* stop and exit wps */
637 case ACTION_WPS_STOP:
638 if (global_settings.party_mode)
639 break;
640 bookmark = true;
641 exit = true;
642 break;
644 case ACTION_WPS_ID3SCREEN:
646 gwps_leave_wps();
647 browse_id3();
648 restore = true;
650 break;
652 case ACTION_REDRAW: /* yes are locked, just redraw */
653 restore = true;
654 break;
655 case ACTION_NONE: /* Timeout */
656 partial_update = true;
657 ffwd_rew(button); /* hopefully fix the ffw/rwd bug */
658 break;
659 #ifdef HAVE_RECORDING
660 case ACTION_WPS_REC:
661 exit = true;
662 break;
663 #endif
664 case SYS_POWEROFF:
665 gwps_leave_wps();
666 default_event_handler(SYS_POWEROFF);
667 break;
669 default:
670 if(default_event_handler(button) == SYS_USB_CONNECTED)
671 return GO_TO_ROOT;
672 update_track = true;
673 break;
676 if (wps_state.do_full_update || partial_update || update_track)
678 if (update_track)
680 wps_state.do_full_update = true;
681 wps_state.id3 = audio_current_track();
682 wps_state.nid3 = audio_next_track();
684 FOR_NB_SCREENS(i)
686 gui_wps_update(&gui_wps[i]);
688 wps_state.do_full_update = false;
689 update_track = false;
690 partial_update = false;
693 if (restore && wps_state.id3 &&
694 ((restoretimer == RESTORE_WPS_INSTANTLY) ||
695 TIME_AFTER(current_tick, restoretimer)))
697 restore = false;
698 restoretimer = RESTORE_WPS_INSTANTLY;
699 FOR_NB_SCREENS(i)
701 screens[i].stop_scroll();
702 gui_wps_display(&gui_wps[i]);
706 if (exit) {
707 #ifdef HAVE_LCD_CHARCELLS
708 status_set_record(false);
709 status_set_audio(false);
710 #endif
711 if (global_settings.fade_on_stop)
712 fade(false, true);
714 if (bookmark)
715 bookmark_autobookmark();
716 audio_stop();
717 #ifdef AB_REPEAT_ENABLE
718 ab_reset_markers();
719 #endif
720 gwps_leave_wps();
721 #ifdef HAVE_RECORDING
722 if (button == ACTION_WPS_REC)
723 return GO_TO_RECSCREEN;
724 #endif
725 if (global_settings.browse_current)
726 return GO_TO_PREVIOUS_BROWSER;
727 return GO_TO_PREVIOUS;
730 if (button && !IS_SYSEVENT(button) )
731 storage_spin();
733 return GO_TO_ROOT; /* unreachable - just to reduce compiler warnings */
736 /* this is called from the playback thread so NO DRAWING! */
737 static void track_changed_callback(void *param)
739 wps_state.id3 = (struct mp3entry*)param;
740 wps_state.nid3 = audio_next_track();
742 if (cuesheet_is_enabled() && wps_state.id3->cuesheet_type
743 && strcmp(wps_state.id3->path, curr_cue->audio_filename))
745 /* the current cuesheet isn't the right one any more */
746 /* We need to parse the new cuesheet */
747 char cuepath[MAX_PATH];
749 if (look_for_cuesheet_file(wps_state.id3->path, cuepath) &&
750 parse_cuesheet(cuepath, curr_cue))
752 wps_state.id3->cuesheet_type = 1;
753 strcpy(curr_cue->audio_filename, wps_state.id3->path);
756 cue_spoof_id3(curr_cue, wps_state.id3);
758 wps_state.do_full_update = true;
760 static void nextid3available_callback(void* param)
762 (void)param;
763 wps_state.nid3 = audio_next_track();
764 wps_state.do_full_update = true;
767 /* wps_state */
769 static void wps_state_init(void)
771 wps_state.ff_rewind = false;
772 wps_state.paused = false;
773 wps_state.id3 = NULL;
774 wps_state.nid3 = NULL;
775 wps_state.do_full_update = true;
776 /* add the WPS track event callbacks */
777 add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, track_changed_callback);
778 add_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, false, nextid3available_callback);
781 /* wps_state end*/
783 #ifdef HAVE_LCD_BITMAP
784 static void statusbar_toggle_handler(void *data)
786 (void)data;
787 int i;
788 gwps_fix_statusbars();
790 FOR_NB_SCREENS(i)
792 struct viewport *vp = &gui_wps[i].data->viewports[0].vp;
793 bool draw = wpsbars & (VP_SB_ONSCREEN(i) | VP_SB_IGNORE_SETTING(i));
794 if (!draw)
796 vp->y = 0;
797 vp->height = screens[i].lcdheight;
799 else
801 vp->y = STATUSBAR_HEIGHT;
802 vp->height = screens[i].lcdheight - STATUSBAR_HEIGHT;
806 #endif
808 void gui_sync_wps_init(void)
810 int i;
811 FOR_NB_SCREENS(i)
813 wps_data_init(&wps_datas[i]);
814 #ifdef HAVE_ALBUMART
815 wps_datas[i].wps_uses_albumart = 0;
816 #endif
817 #ifdef HAVE_REMOTE_LCD
818 wps_datas[i].remote_wps = (i != 0);
819 #endif
820 gui_wps[i].data = &wps_datas[i];
821 gui_wps[i].display = &screens[i];
822 /* Currently no seperate wps_state needed/possible
823 so use the only aviable ( "global" ) one */
824 gui_wps[i].state = &wps_state;
826 #ifdef HAVE_LCD_BITMAP
827 add_event(GUI_EVENT_STATUSBAR_TOGGLE, false, statusbar_toggle_handler);
828 #endif
829 #if LCD_DEPTH > 1
830 unload_wps_backdrop();
831 #endif
832 #if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
833 unload_remote_wps_backdrop();
834 #endif
837 #ifdef HAVE_ALBUMART
838 /* Returns true if at least one of the gui_wps screens has an album art
839 tag in its wps structure */
840 bool gui_sync_wps_uses_albumart(void)
842 int i;
843 FOR_NB_SCREENS(i) {
844 struct gui_wps *gwps = &gui_wps[i];
845 if (gwps->data && (gwps->data->wps_uses_albumart != WPS_ALBUMART_NONE))
846 return true;
848 return false;
850 #endif