diff options
author | William Wilgus <wilgus.william@gmail.com> | 2020-07-15 12:00:09 -0400 |
---|---|---|
committer | William Wilgus <wilgus.william@gmail.com> | 2020-07-27 20:47:03 -0400 |
commit | be04c4be0afbdef36f2968dc291af54360de8e23 (patch) | |
tree | 8b4e87d48b38bf98f4f7eb8bcbd62cc196cbbaff /apps | |
parent | 3f828e9244140e7860e961917fc96e42b2a866ca (diff) | |
download | rockbox-be04c4be0afbdef36f2968dc291af54360de8e23.tar.gz rockbox-be04c4be0afbdef36f2968dc291af54360de8e23.zip |
Voice TSR Plugin Demo
allows user to run plugin in background that voices
status messages
grouping is now working it counts ; as the end of a group
sleep timer remaining is not voiced if sleep timer is not active
TODO
manual entries
Change-Id: I39e8500df6440c07d2a3347513c749d5e155d1cc
Diffstat (limited to 'apps')
-rw-r--r-- | apps/lang/english.lang | 206 | ||||
-rw-r--r-- | apps/plugins/CATEGORIES | 1 | ||||
-rw-r--r-- | apps/plugins/SOURCES | 5 | ||||
-rw-r--r-- | apps/plugins/announce_status.c | 842 |
4 files changed, 1054 insertions, 0 deletions
diff --git a/apps/lang/english.lang b/apps/lang/english.lang index a424fd1e7f..8079884426 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang | |||
@@ -15904,3 +15904,209 @@ | |||
15904 | xduoox20,xduoox3,xduoox3ii: "Double tap HOME to cancel." | 15904 | xduoox20,xduoox3,xduoox3ii: "Double tap HOME to cancel." |
15905 | </voice> | 15905 | </voice> |
15906 | </phrase> | 15906 | </phrase> |
15907 | <phrase> | ||
15908 | id: LANG_DATE | ||
15909 | desc: for constructing time and date announcements | ||
15910 | user: core | ||
15911 | <source> | ||
15912 | *: "Date" | ||
15913 | </source> | ||
15914 | <dest> | ||
15915 | *: "Date" | ||
15916 | </dest> | ||
15917 | <voice> | ||
15918 | *: "Date" | ||
15919 | </voice> | ||
15920 | </phrase> | ||
15921 | <phrase> | ||
15922 | id: LANG_CLEAR_ALL | ||
15923 | desc: | ||
15924 | user: core | ||
15925 | <source> | ||
15926 | *: "Clear all" | ||
15927 | </source> | ||
15928 | <dest> | ||
15929 | *: "Clear all" | ||
15930 | </dest> | ||
15931 | <voice> | ||
15932 | *: "Clear all" | ||
15933 | </voice> | ||
15934 | </phrase> | ||
15935 | <phrase> | ||
15936 | id: LANG_CANCEL_0 | ||
15937 | desc: CANCEL. | ||
15938 | user: core | ||
15939 | <source> | ||
15940 | *: "Cancel" | ||
15941 | </source> | ||
15942 | <dest> | ||
15943 | *: "Cancel" | ||
15944 | </dest> | ||
15945 | <voice> | ||
15946 | *: "Cancel" | ||
15947 | </voice> | ||
15948 | </phrase> | ||
15949 | <phrase> | ||
15950 | id: LANG_SAVE | ||
15951 | desc: | ||
15952 | user: core | ||
15953 | <source> | ||
15954 | *: "Save" | ||
15955 | </source> | ||
15956 | <dest> | ||
15957 | *: "Save" | ||
15958 | </dest> | ||
15959 | <voice> | ||
15960 | *: "Save" | ||
15961 | </voice> | ||
15962 | </phrase> | ||
15963 | <phrase> | ||
15964 | id: LANG_TIMEOUT | ||
15965 | desc: | ||
15966 | user: core | ||
15967 | <source> | ||
15968 | *: "Timeout" | ||
15969 | </source> | ||
15970 | <dest> | ||
15971 | *: "Timeout" | ||
15972 | </dest> | ||
15973 | <voice> | ||
15974 | *: "Timeout" | ||
15975 | </voice> | ||
15976 | </phrase> | ||
15977 | <phrase> | ||
15978 | id: LANG_TRACK | ||
15979 | desc: used in track x of y constructs | ||
15980 | user: core | ||
15981 | <source> | ||
15982 | *: none | ||
15983 | hotkey: "Track" | ||
15984 | </source> | ||
15985 | <dest> | ||
15986 | *: none | ||
15987 | hotkey: "Track" | ||
15988 | </dest> | ||
15989 | <voice> | ||
15990 | *: none | ||
15991 | hotkey: "Track" | ||
15992 | </voice> | ||
15993 | </phrase> | ||
15994 | <phrase> | ||
15995 | id: LANG_ELAPSED | ||
15996 | desc: prefix for elapsed playtime announcement | ||
15997 | user: core | ||
15998 | <source> | ||
15999 | *: none | ||
16000 | hotkey: "Elapsed" | ||
16001 | </source> | ||
16002 | <dest> | ||
16003 | *: none | ||
16004 | hotkey: "Elapsed" | ||
16005 | </dest> | ||
16006 | <voice> | ||
16007 | *: none | ||
16008 | hotkey: "Elapsed" | ||
16009 | </voice> | ||
16010 | </phrase> | ||
16011 | <phrase> | ||
16012 | id: LANG_ANNOUNCEMENT_FMT | ||
16013 | desc: format for wps hotkey announcement | ||
16014 | user: core | ||
16015 | <source> | ||
16016 | *: none | ||
16017 | hotkey: "Announcement format" | ||
16018 | </source> | ||
16019 | <dest> | ||
16020 | *: none | ||
16021 | hotkey: "Announcement format" | ||
16022 | </dest> | ||
16023 | <voice> | ||
16024 | *: none | ||
16025 | hotkey: "Announcement format" | ||
16026 | </voice> | ||
16027 | </phrase> | ||
16028 | <phrase> | ||
16029 | id: LANG_REMAIN | ||
16030 | desc: for constructs such as number of tracks remaining etc | ||
16031 | user: core | ||
16032 | <source> | ||
16033 | *: none | ||
16034 | hotkey: "Remain" | ||
16035 | </source> | ||
16036 | <dest> | ||
16037 | *: none | ||
16038 | hotkey: "Remain" | ||
16039 | </dest> | ||
16040 | <voice> | ||
16041 | *: none | ||
16042 | hotkey: "Remain" | ||
16043 | </voice> | ||
16044 | </phrase> | ||
16045 | <phrase> | ||
16046 | id: LANG_GROUPING | ||
16047 | desc: | ||
16048 | user: core | ||
16049 | <source> | ||
16050 | *: none | ||
16051 | hotkey: "Grouping" | ||
16052 | </source> | ||
16053 | <dest> | ||
16054 | *: none | ||
16055 | hotkey: "Grouping" | ||
16056 | </dest> | ||
16057 | <voice> | ||
16058 | *: none | ||
16059 | hotkey: "Grouping" | ||
16060 | </voice> | ||
16061 | </phrase> | ||
16062 | <phrase> | ||
16063 | id: LANG_ANNOUNCE_ON | ||
16064 | desc: | ||
16065 | user: core | ||
16066 | <source> | ||
16067 | *: none | ||
16068 | hotkey: "Announce on" | ||
16069 | </source> | ||
16070 | <dest> | ||
16071 | *: none | ||
16072 | hotkey: "Announce on" | ||
16073 | </dest> | ||
16074 | <voice> | ||
16075 | *: none | ||
16076 | hotkey: "Announce on" | ||
16077 | </voice> | ||
16078 | </phrase> | ||
16079 | <phrase> | ||
16080 | id: LANG_TRACK_CHANGE | ||
16081 | desc: | ||
16082 | user: core | ||
16083 | <source> | ||
16084 | *: none | ||
16085 | hotkey: "Track change" | ||
16086 | </source> | ||
16087 | <dest> | ||
16088 | *: none | ||
16089 | hotkey: "Track change" | ||
16090 | </dest> | ||
16091 | <voice> | ||
16092 | *: none | ||
16093 | hotkey: "Track change" | ||
16094 | </voice> | ||
16095 | </phrase> | ||
16096 | <phrase> | ||
16097 | id: LANG_HOLD_FOR_SETTINGS | ||
16098 | desc: | ||
16099 | user: core | ||
16100 | <source> | ||
16101 | *: none | ||
16102 | hotkey: "Hold for settings" | ||
16103 | </source> | ||
16104 | <dest> | ||
16105 | *: none | ||
16106 | hotkey: "Hold for settings" | ||
16107 | </dest> | ||
16108 | <voice> | ||
16109 | *: none | ||
16110 | hotkey: "Hold for settings" | ||
16111 | </voice> | ||
16112 | </phrase> | ||
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index bb8688347d..07bc4df847 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES | |||
@@ -1,6 +1,7 @@ | |||
1 | 2048,games | 1 | 2048,games |
2 | alpine_cdc,apps | 2 | alpine_cdc,apps |
3 | alarmclock,apps | 3 | alarmclock,apps |
4 | announce_status,demos | ||
4 | autostart,apps | 5 | autostart,apps |
5 | battery_bench,apps | 6 | battery_bench,apps |
6 | bench_scaler,apps | 7 | bench_scaler,apps |
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 5d24bee8f6..0180c56faf 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES | |||
@@ -151,6 +151,11 @@ starfield.c | |||
151 | vu_meter.c | 151 | vu_meter.c |
152 | wormlet.c | 152 | wormlet.c |
153 | 153 | ||
154 | #ifdef HAVE_HOTKEY | ||
155 | announce_status.c | ||
156 | #endif | ||
157 | |||
158 | |||
154 | /* Plugins needing the grayscale lib on low-depth LCDs */ | 159 | /* Plugins needing the grayscale lib on low-depth LCDs */ |
155 | fire.c | 160 | fire.c |
156 | plasma.c | 161 | plasma.c |
diff --git a/apps/plugins/announce_status.c b/apps/plugins/announce_status.c new file mode 100644 index 0000000000..e46899f8cd --- /dev/null +++ b/apps/plugins/announce_status.c | |||
@@ -0,0 +1,842 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * | ||
11 | * Copyright (C) 2003-2005 Jörg Hohensohn | ||
12 | * Copyright (C) 2020 BILGUS | ||
13 | * | ||
14 | * | ||
15 | * | ||
16 | * Usage: Start plugin, it will stay in the background. | ||
17 | * | ||
18 | * | ||
19 | * This program is free software; you can redistribute it and/or | ||
20 | * modify it under the terms of the GNU General Public License | ||
21 | * as published by the Free Software Foundation; either version 2 | ||
22 | * of the License, or (at your option) any later version. | ||
23 | * | ||
24 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
25 | * KIND, either express or implied. | ||
26 | * | ||
27 | ****************************************************************************/ | ||
28 | |||
29 | #include "plugin.h" | ||
30 | #include "lib/kbd_helper.h" | ||
31 | #include "lib/configfile.h" | ||
32 | |||
33 | /****************** constants ******************/ | ||
34 | #define MAX_GROUPS 7 | ||
35 | #define MAX_ANNOUNCE_WPS 63 | ||
36 | #define ANNOUNCEMENT_TIMEOUT 10 | ||
37 | #define GROUPING_CHAR ';' | ||
38 | |||
39 | #define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFF) | ||
40 | #define EV_OTHINSTANCE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFE) | ||
41 | #define EV_STARTUP MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x01) | ||
42 | #define EV_TRACKCHANGE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x02) | ||
43 | |||
44 | #define CFG_FILE "/VoiceTSR.cfg" | ||
45 | #define CFG_VER 1 | ||
46 | |||
47 | #if CONFIG_RTC | ||
48 | #define K_TIME "DT D1;\n\n" | ||
49 | #define K_DATE "DD D2;\n\n" | ||
50 | #else | ||
51 | #define K_TIME "" | ||
52 | #define K_DATE "" | ||
53 | #endif | ||
54 | |||
55 | #define K_TRACK_TA "TT TA;\n" | ||
56 | #define K_TRACK "TE TL TR;\n" | ||
57 | #define K_TRACK1 "T1 T2 T3;\n\n" | ||
58 | #define K_PLAYLIST "PC PN PR P1 P2;\n" | ||
59 | #define K_BATTERY "BP BM B1;\n" | ||
60 | #define K_SLEEP "RS R2 R3;\n" | ||
61 | #define K_RUNTIME "RT R1;" | ||
62 | #define KEYBD_LAYOUT (K_TIME K_DATE K_TRACK_TA K_TRACK K_TRACK1 K_PLAYLIST K_BATTERY K_SLEEP K_RUNTIME) | ||
63 | |||
64 | /****************** prototypes ******************/ | ||
65 | void print_scroll(char* string); /* implements a scrolling screen */ | ||
66 | |||
67 | int get_playtime(void); /* return the current track time in seconds */ | ||
68 | int get_tracklength(void); /* return the total length of the current track */ | ||
69 | int get_track(void); /* return the track number */ | ||
70 | void get_playmsg(void); /* update the play message with Rockbox info */ | ||
71 | |||
72 | void thread_create(void); | ||
73 | void thread(void); /* the thread running it all */ | ||
74 | void thread_quit(void); | ||
75 | static int voice_general_info(bool testing); | ||
76 | static unsigned char* voice_info_group(unsigned char* current_token, bool testing); | ||
77 | |||
78 | int main(const void* parameter); /* main loop */ | ||
79 | enum plugin_status plugin_start(const void* parameter); /* entry */ | ||
80 | |||
81 | |||
82 | /****************** data types ******************/ | ||
83 | |||
84 | /****************** globals ******************/ | ||
85 | /* communication to the worker thread */ | ||
86 | static struct | ||
87 | { | ||
88 | bool exiting; /* signal to the thread that we want to exit */ | ||
89 | unsigned int id; /* worker thread id */ | ||
90 | struct event_queue queue; /* thread event queue */ | ||
91 | long *stack; | ||
92 | ssize_t stacksize; | ||
93 | void *buf; | ||
94 | size_t buf_size; | ||
95 | |||
96 | } gThread; | ||
97 | |||
98 | static struct | ||
99 | { | ||
100 | int interval; | ||
101 | int announce_on; | ||
102 | int grouping; | ||
103 | |||
104 | int timeout; | ||
105 | int count; | ||
106 | unsigned int index; | ||
107 | int bin_added; | ||
108 | |||
109 | unsigned char wps_fmt[MAX_ANNOUNCE_WPS+1]; | ||
110 | } gAnnounce; | ||
111 | |||
112 | static struct configdata config[] = | ||
113 | { | ||
114 | {TYPE_INT, 0, 10000, { .int_p = &gAnnounce.interval }, "Interval", NULL}, | ||
115 | {TYPE_INT, 0, 2, { .int_p = &gAnnounce.announce_on }, "Announce", NULL}, | ||
116 | {TYPE_INT, 0, 10, { .int_p = &gAnnounce.grouping }, "Grouping", NULL}, | ||
117 | {TYPE_INT, 0, 10000, { .int_p = &gAnnounce.bin_added }, "Added", NULL}, | ||
118 | {TYPE_STRING, 0, MAX_ANNOUNCE_WPS+1, | ||
119 | { .string = (char*)&gAnnounce.wps_fmt }, "Fmt", NULL}, | ||
120 | }; | ||
121 | |||
122 | const int gCfg_sz = sizeof(config)/sizeof(*config); | ||
123 | /****************** communication with Rockbox playback ******************/ | ||
124 | |||
125 | #if 0 | ||
126 | /* return the track number */ | ||
127 | int get_track(void) | ||
128 | { | ||
129 | //if (rb->audio_status() == (AUDIO_STATUS_PLAY | AUDIO_STATUS_PAUSE)) | ||
130 | struct mp3entry* p_mp3entry; | ||
131 | |||
132 | p_mp3entry = rb->audio_current_track(); | ||
133 | if (p_mp3entry == NULL) | ||
134 | return 0; | ||
135 | |||
136 | return p_mp3entry->index + 1; /* track numbers start with 1 */ | ||
137 | } | ||
138 | #endif | ||
139 | |||
140 | static void playback_event_callback(unsigned short id, void *data) | ||
141 | { | ||
142 | (void)id; | ||
143 | (void)data; | ||
144 | rb->queue_post(&gThread.queue, EV_TRACKCHANGE, 0); | ||
145 | } | ||
146 | |||
147 | /****************** config functions *****************/ | ||
148 | static void config_set_defaults(void) | ||
149 | { | ||
150 | gAnnounce.bin_added = 0; | ||
151 | gAnnounce.interval = ANNOUNCEMENT_TIMEOUT; | ||
152 | gAnnounce.announce_on = 0; | ||
153 | gAnnounce.grouping = 0; | ||
154 | gAnnounce.wps_fmt[0] = '\0'; | ||
155 | } | ||
156 | |||
157 | static void config_reset_voice(void) | ||
158 | { | ||
159 | /* don't want to change these so save a copy */ | ||
160 | int interval = gAnnounce.interval; | ||
161 | int announce = gAnnounce.announce_on; | ||
162 | int grouping = gAnnounce.grouping; | ||
163 | |||
164 | if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) | ||
165 | { | ||
166 | rb->splash(100, "ERROR!"); | ||
167 | return; | ||
168 | } | ||
169 | |||
170 | /* restore other settings */ | ||
171 | gAnnounce.interval = interval; | ||
172 | gAnnounce.announce_on = announce; | ||
173 | gAnnounce.grouping = grouping; | ||
174 | } | ||
175 | |||
176 | /****************** helper fuctions ******************/ | ||
177 | |||
178 | void announce(void) | ||
179 | { | ||
180 | rb->talk_force_shutup(); | ||
181 | rb->sleep(HZ / 2); | ||
182 | voice_general_info(false); | ||
183 | //rb->talk_force_enqueue_next(); | ||
184 | } | ||
185 | |||
186 | static void announce_test(void) | ||
187 | { | ||
188 | rb->talk_force_shutup(); | ||
189 | rb->sleep(HZ / 2); | ||
190 | voice_info_group(gAnnounce.wps_fmt, true); | ||
191 | |||
192 | //rb->talk_force_enqueue_next(); | ||
193 | } | ||
194 | |||
195 | static void announce_add(const char *str) | ||
196 | { | ||
197 | int len_cur = rb->strlen(gAnnounce.wps_fmt); | ||
198 | int len_str = rb->strlen(str); | ||
199 | if (len_cur + len_str > MAX_ANNOUNCE_WPS) | ||
200 | return; | ||
201 | rb->strcpy(&gAnnounce.wps_fmt[len_cur], str); | ||
202 | announce_test(); | ||
203 | |||
204 | } | ||
205 | |||
206 | static int _playlist_get_display_index(struct playlist_info *playlist) | ||
207 | { | ||
208 | /* equivalent of the function found in playlist.c */ | ||
209 | if(!playlist) | ||
210 | return -1; | ||
211 | /* first_index should always be index 0 for display purposes */ | ||
212 | int index = playlist->index; | ||
213 | index -= playlist->first_index; | ||
214 | if (index < 0) | ||
215 | index += playlist->amount; | ||
216 | |||
217 | return index + 1; | ||
218 | } | ||
219 | |||
220 | static enum themable_icons icon_callback(int selected_item, void * data) | ||
221 | { | ||
222 | (void)data; | ||
223 | |||
224 | if(selected_item < MAX_GROUPS && selected_item >= 0) | ||
225 | { | ||
226 | int bin = 1 << (selected_item); | ||
227 | if ((gAnnounce.bin_added & bin) == bin) | ||
228 | return Icon_Submenu; | ||
229 | } | ||
230 | |||
231 | return Icon_NOICON; | ||
232 | } | ||
233 | |||
234 | static int announce_menu_cb(int action, | ||
235 | const struct menu_item_ex *this_item, | ||
236 | struct gui_synclist *this_list) | ||
237 | { | ||
238 | (void)this_item; | ||
239 | unsigned short *kbd_p = gThread.buf; | ||
240 | size_t kbd_bufsz = gThread.buf_size; | ||
241 | |||
242 | int selection = rb->gui_synclist_get_sel_pos(this_list); | ||
243 | |||
244 | if(action == ACTION_ENTER_MENUITEM) | ||
245 | { | ||
246 | rb->gui_synclist_set_icon_callback(this_list, icon_callback); | ||
247 | } | ||
248 | else if ((action == ACTION_STD_OK)) | ||
249 | { | ||
250 | //rb->splashf(100, "%d", selection); | ||
251 | if (selection < MAX_GROUPS && selection >= 0) /* only add premade tags once */ | ||
252 | { | ||
253 | int bin = 1 << (selection); | ||
254 | if ((gAnnounce.bin_added & bin) == bin) | ||
255 | return 0; | ||
256 | |||
257 | gAnnounce.bin_added |= bin; | ||
258 | } | ||
259 | |||
260 | switch(selection) { | ||
261 | |||
262 | case 0: /*Time*/ | ||
263 | announce_add("D1Dt ;"); | ||
264 | break; | ||
265 | case 1: /*Date*/ | ||
266 | announce_add("D2Dd ;"); | ||
267 | break; | ||
268 | case 2: /*Track*/ | ||
269 | announce_add("TT T1TeT2Tr ;"); | ||
270 | break; | ||
271 | case 3: /*Playlist*/ | ||
272 | announce_add("P1PC P2PN ;"); | ||
273 | break; | ||
274 | case 4: /*Battery*/ | ||
275 | announce_add("B1Bp ;"); | ||
276 | break; | ||
277 | case 5: /*Sleep*/ | ||
278 | announce_add("R2RsR3 ;"); | ||
279 | break; | ||
280 | case 6: /*Runtime*/ | ||
281 | announce_add("R1Rt ;"); | ||
282 | break; | ||
283 | case 7: /* sep */ | ||
284 | break; | ||
285 | case 8: /*Clear All*/ | ||
286 | gAnnounce.wps_fmt[0] = '\0'; | ||
287 | gAnnounce.bin_added = 0; | ||
288 | rb->splash(HZ / 2, ID2P(LANG_RESET_DONE_CLEAR)); | ||
289 | break; | ||
290 | case 9: /* inspect it */ | ||
291 | if (!kbd_create_layout(KEYBD_LAYOUT, kbd_p, kbd_bufsz)) | ||
292 | kbd_p = NULL; | ||
293 | |||
294 | rb->kbd_input(gAnnounce.wps_fmt, MAX_ANNOUNCE_WPS, kbd_p); | ||
295 | break; | ||
296 | case 10: /*test it*/ | ||
297 | announce_test(); | ||
298 | break; | ||
299 | case 11: /*cancel*/ | ||
300 | config_reset_voice(); | ||
301 | return ACTION_STD_CANCEL; | ||
302 | case 12: /* save */ | ||
303 | return ACTION_STD_CANCEL; | ||
304 | default: | ||
305 | return action; | ||
306 | } | ||
307 | rb->gui_synclist_draw(this_list); /* redraw */ | ||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | return action; | ||
312 | } | ||
313 | |||
314 | static int announce_menu(void) | ||
315 | { | ||
316 | int selection = 0; | ||
317 | |||
318 | MENUITEM_STRINGLIST(announce_menu, "Announcements", announce_menu_cb, | ||
319 | ID2P(LANG_TIME), | ||
320 | ID2P(LANG_DATE), | ||
321 | ID2P(LANG_TRACK), | ||
322 | ID2P(LANG_PLAYLIST), | ||
323 | ID2P(LANG_BATTERY_MENU), | ||
324 | ID2P(LANG_SLEEP_TIMER), | ||
325 | ID2P(LANG_RUNNING_TIME), | ||
326 | ID2P(VOICE_BLANK), | ||
327 | ID2P(LANG_CLEAR_ALL), | ||
328 | ID2P(LANG_ANNOUNCEMENT_FMT), | ||
329 | ID2P(LANG_VOICE), | ||
330 | ID2P(LANG_CANCEL_0), | ||
331 | ID2P(LANG_SAVE)); | ||
332 | |||
333 | selection = rb->do_menu(&announce_menu, &selection, NULL, true); | ||
334 | if (selection == MENU_ATTACHED_USB) | ||
335 | return PLUGIN_USB_CONNECTED; | ||
336 | |||
337 | return 0; | ||
338 | } | ||
339 | |||
340 | /** | ||
341 | Shows the settings menu | ||
342 | */ | ||
343 | static int settings_menu(void) | ||
344 | { | ||
345 | int selection = 0; | ||
346 | //bool old_val; | ||
347 | |||
348 | MENUITEM_STRINGLIST(settings_menu, "Announce Settings", NULL, | ||
349 | ID2P(LANG_TIMEOUT), | ||
350 | ID2P(LANG_ANNOUNCE_ON), | ||
351 | ID2P(LANG_GROUPING), | ||
352 | ID2P(LANG_ANNOUNCEMENT_FMT), | ||
353 | ID2P(VOICE_BLANK), | ||
354 | ID2P(LANG_MENU_QUIT), | ||
355 | ID2P(LANG_SAVE_EXIT)); | ||
356 | |||
357 | static const struct opt_items announce_options[] = { | ||
358 | { STR(LANG_OFF)}, | ||
359 | { STR(LANG_TRACK_CHANGE)}, | ||
360 | }; | ||
361 | |||
362 | do { | ||
363 | selection=rb->do_menu(&settings_menu,&selection, NULL, true); | ||
364 | switch(selection) { | ||
365 | |||
366 | case 0: | ||
367 | rb->set_int(rb->str(LANG_TIMEOUT), "s", UNIT_SEC, | ||
368 | &gAnnounce.interval, NULL, 1, 1, 360, NULL ); | ||
369 | break; | ||
370 | case 1: | ||
371 | rb->set_option(rb->str(LANG_ANNOUNCE_ON), | ||
372 | &gAnnounce.announce_on, INT, announce_options, 2, NULL); | ||
373 | break; | ||
374 | case 2: | ||
375 | rb->set_int(rb->str(LANG_GROUPING), "", 1, | ||
376 | &gAnnounce.grouping, NULL, 1, 0, 7, NULL ); | ||
377 | break; | ||
378 | case 3: | ||
379 | announce_menu(); | ||
380 | break; | ||
381 | case 4: /*sep*/ | ||
382 | continue; | ||
383 | case 5: | ||
384 | return -1; | ||
385 | break; | ||
386 | case 6: | ||
387 | configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); | ||
388 | return 0; | ||
389 | break; | ||
390 | |||
391 | case MENU_ATTACHED_USB: | ||
392 | return PLUGIN_USB_CONNECTED; | ||
393 | default: | ||
394 | return 0; | ||
395 | } | ||
396 | } while ( selection >= 0 ); | ||
397 | return 0; | ||
398 | } | ||
399 | |||
400 | |||
401 | /****************** main thread + helper ******************/ | ||
402 | void thread(void) | ||
403 | { | ||
404 | long interval; | ||
405 | long last_tick = *rb->current_tick; /* for 1 sec tick */ | ||
406 | |||
407 | struct queue_event ev; | ||
408 | while (!gThread.exiting) | ||
409 | { | ||
410 | rb->queue_wait(&gThread.queue, &ev); | ||
411 | interval = gAnnounce.interval * HZ; | ||
412 | switch (ev.id) | ||
413 | { | ||
414 | case EV_EXIT: | ||
415 | return; | ||
416 | case EV_OTHINSTANCE: | ||
417 | if (*rb->current_tick - last_tick >= interval) | ||
418 | { | ||
419 | last_tick += interval; | ||
420 | rb->sleep(0); | ||
421 | announce(); | ||
422 | } | ||
423 | break; | ||
424 | case EV_STARTUP: | ||
425 | rb->beep_play(1500, 100, 1000); | ||
426 | break; | ||
427 | case EV_TRACKCHANGE: | ||
428 | rb->sleep(0); | ||
429 | announce(); | ||
430 | break; | ||
431 | } | ||
432 | } | ||
433 | } | ||
434 | |||
435 | void plugin_buffer_init(void) | ||
436 | { | ||
437 | if (gThread.buf == 0) | ||
438 | { | ||
439 | rb->memset(&gThread, 0, sizeof(gThread)); | ||
440 | gThread.buf = rb->plugin_get_buffer(&gThread.buf_size); | ||
441 | ALIGN_BUFFER(gThread.buf, gThread.buf_size, 4); | ||
442 | } | ||
443 | } | ||
444 | |||
445 | void thread_create(void) | ||
446 | { | ||
447 | /* init the worker thread */ | ||
448 | gThread.stacksize = gThread.buf_size; | ||
449 | gThread.buf_size -= gThread.stacksize; | ||
450 | |||
451 | gThread.stack = (long *) gThread.buf + gThread.buf_size; | ||
452 | |||
453 | ALIGN_BUFFER(gThread.stack, gThread.stacksize, 4); | ||
454 | |||
455 | if (gThread.stacksize < DEFAULT_STACK_SIZE) | ||
456 | { | ||
457 | rb->splash(HZ*2, "Out of memory"); | ||
458 | gThread.exiting = true; | ||
459 | gThread.id = UINT_MAX; | ||
460 | return; | ||
461 | } | ||
462 | |||
463 | |||
464 | /* put the thread's queue in the bcast list */ | ||
465 | rb->queue_init(&gThread.queue, true); | ||
466 | |||
467 | gThread.id = rb->create_thread(thread, gThread.stack, gThread.stacksize, | ||
468 | 0, "vTSR" | ||
469 | IF_PRIO(, PRIORITY_BACKGROUND) | ||
470 | IF_COP(, CPU)); | ||
471 | rb->queue_post(&gThread.queue, EV_STARTUP, 0); | ||
472 | rb->yield(); | ||
473 | |||
474 | } | ||
475 | |||
476 | void thread_quit(void) | ||
477 | { | ||
478 | if (!gThread.exiting) { | ||
479 | rb->queue_post(&gThread.queue, EV_EXIT, 0); | ||
480 | rb->thread_wait(gThread.id); | ||
481 | /* remove the thread's queue from the broadcast list */ | ||
482 | rb->queue_delete(&gThread.queue); | ||
483 | gThread.exiting = true; | ||
484 | } | ||
485 | } | ||
486 | |||
487 | /* callback to end the TSR plugin, called before a new one gets loaded */ | ||
488 | static bool exit_tsr(bool reenter) | ||
489 | { | ||
490 | if (reenter) | ||
491 | { | ||
492 | rb->queue_post(&gThread.queue, EV_OTHINSTANCE, 0); | ||
493 | return false; /* dont let it start again */ | ||
494 | } | ||
495 | thread_quit(); | ||
496 | |||
497 | return true; | ||
498 | } | ||
499 | |||
500 | |||
501 | /****************** main ******************/ | ||
502 | |||
503 | int plugin_main(const void* parameter) | ||
504 | { | ||
505 | (void)parameter; | ||
506 | bool settings = false; | ||
507 | int i = 0; | ||
508 | |||
509 | gAnnounce.index = 0; | ||
510 | gAnnounce.timeout = 0; | ||
511 | |||
512 | rb->talk_id(LANG_HOLD_FOR_SETTINGS, false); | ||
513 | rb->splash(HZ / 2, "Announce Status"); | ||
514 | |||
515 | if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) | ||
516 | { | ||
517 | /* If the loading failed, save a new config file */ | ||
518 | config_set_defaults(); | ||
519 | configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); | ||
520 | |||
521 | rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS)); | ||
522 | } | ||
523 | |||
524 | rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS)); | ||
525 | |||
526 | rb->button_clear_queue(); | ||
527 | if (rb->button_get_w_tmo(HZ) > BUTTON_NONE) | ||
528 | { | ||
529 | while ((rb->button_get(false) & BUTTON_REL) != BUTTON_REL) | ||
530 | { | ||
531 | if (i & 1) | ||
532 | rb->beep_play(800, 100, 1000); | ||
533 | |||
534 | if (++i > 15) | ||
535 | { | ||
536 | settings = true; | ||
537 | break; | ||
538 | } | ||
539 | sleep(HZ / 5); | ||
540 | } | ||
541 | } | ||
542 | |||
543 | plugin_buffer_init(); /* need buffer for custom keyboard layout */ | ||
544 | |||
545 | if (settings) | ||
546 | { | ||
547 | rb->splash(100, ID2P(LANG_SETTINGS)); | ||
548 | int ret = settings_menu(); | ||
549 | if (ret < 0) | ||
550 | return 0; | ||
551 | } | ||
552 | |||
553 | gAnnounce.timeout = *rb->current_tick; | ||
554 | rb->plugin_tsr(exit_tsr); /* stay resident */ | ||
555 | |||
556 | if (gAnnounce.announce_on == 1) | ||
557 | rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, playback_event_callback); | ||
558 | |||
559 | thread_create(); | ||
560 | #ifdef DEBUG | ||
561 | return rb->default_event_handler(button); | ||
562 | #else | ||
563 | return 0; | ||
564 | #endif | ||
565 | } | ||
566 | |||
567 | |||
568 | /***************** Plugin Entry Point *****************/ | ||
569 | |||
570 | |||
571 | enum plugin_status plugin_start(const void* parameter) | ||
572 | { | ||
573 | /* now go ahead and have fun! */ | ||
574 | int ret = plugin_main(parameter); | ||
575 | |||
576 | rb->remove_event(PLAYBACK_EVENT_START_PLAYBACK, playback_event_callback); | ||
577 | return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR; | ||
578 | } | ||
579 | |||
580 | static int voice_general_info(bool testing) | ||
581 | { | ||
582 | unsigned char* infotemplate = gAnnounce.wps_fmt; | ||
583 | |||
584 | if (gAnnounce.index >= rb->strlen(gAnnounce.wps_fmt)) | ||
585 | gAnnounce.index = 0; | ||
586 | |||
587 | long current_tick = *rb->current_tick; | ||
588 | |||
589 | if (*infotemplate == 0) | ||
590 | { | ||
591 | #if CONFIG_RTC | ||
592 | /* announce the time */ | ||
593 | voice_info_group("D1Dt ", false); | ||
594 | #else | ||
595 | /* announce elapsed play for this track */ | ||
596 | voice_info_group("T1Te ", false); | ||
597 | #endif | ||
598 | return -1; | ||
599 | } | ||
600 | |||
601 | if (TIME_BEFORE(current_tick, gAnnounce.timeout)) | ||
602 | { | ||
603 | return -2; | ||
604 | } | ||
605 | |||
606 | gAnnounce.timeout = current_tick + gAnnounce.interval * HZ; | ||
607 | |||
608 | rb->talk_shutup(); | ||
609 | |||
610 | gAnnounce.count = 0; | ||
611 | infotemplate = voice_info_group(&infotemplate[gAnnounce.index], testing); | ||
612 | gAnnounce.index = (infotemplate - gAnnounce.wps_fmt) + 1; | ||
613 | |||
614 | return 0; | ||
615 | } | ||
616 | |||
617 | static unsigned char* voice_info_group(unsigned char* current_token, bool testing) | ||
618 | { | ||
619 | unsigned char current_char; | ||
620 | bool skip_next_group = false; | ||
621 | gAnnounce.count = 0; | ||
622 | |||
623 | while (*current_token != 0) | ||
624 | { | ||
625 | //rb->splash(10, current_token); | ||
626 | current_char = toupper(*current_token); | ||
627 | if (current_char == 'D') | ||
628 | { | ||
629 | /* | ||
630 | Date and time functions | ||
631 | */ | ||
632 | current_token++; | ||
633 | |||
634 | current_char = toupper(*current_token); | ||
635 | |||
636 | #if CONFIG_RTC | ||
637 | struct tm *tm = rb->get_time(); | ||
638 | |||
639 | if (true) //(valid_time(tm)) | ||
640 | { | ||
641 | if (current_char == 'T') | ||
642 | { | ||
643 | rb->talk_time(tm, true); | ||
644 | } | ||
645 | else if (current_char == 'D') | ||
646 | { | ||
647 | rb->talk_date(tm, true); | ||
648 | } | ||
649 | /* prefix suffix connectives */ | ||
650 | else if (current_char == '1') | ||
651 | { | ||
652 | rb->talk_id(LANG_TIME, true); | ||
653 | } | ||
654 | else if (current_char == '2') | ||
655 | { | ||
656 | rb->talk_id(LANG_DATE, true); | ||
657 | } | ||
658 | } | ||
659 | #endif | ||
660 | } | ||
661 | else if (current_char == 'R') | ||
662 | { | ||
663 | /* | ||
664 | Sleep timer and runtime | ||
665 | */ | ||
666 | int sleep_remaining = sleep_remaining = rb->get_sleep_timer(); | ||
667 | int runtime; | ||
668 | |||
669 | current_token++; | ||
670 | current_char = toupper(*current_token); | ||
671 | if (current_char == 'T') | ||
672 | { | ||
673 | runtime = rb->global_status->runtime; | ||
674 | rb->talk_value(runtime, UNIT_TIME, true); | ||
675 | } | ||
676 | /* prefix suffix connectives */ | ||
677 | else if (current_char == '1') | ||
678 | { | ||
679 | rb->talk_id(LANG_RUNNING_TIME, true); | ||
680 | } | ||
681 | else if (testing || sleep_remaining > 0) | ||
682 | { | ||
683 | if (current_char == 'S') | ||
684 | { | ||
685 | rb->talk_value(sleep_remaining, UNIT_TIME, true); | ||
686 | } | ||
687 | /* prefix suffix connectives */ | ||
688 | else if (current_char == '2') | ||
689 | { | ||
690 | rb->talk_id(LANG_SLEEP_TIMER, true); | ||
691 | } | ||
692 | else if (current_char == '3') | ||
693 | { | ||
694 | rb->talk_id(LANG_REMAIN, true); | ||
695 | } | ||
696 | } | ||
697 | else if (sleep_remaining == 0) | ||
698 | { | ||
699 | skip_next_group = true; | ||
700 | } | ||
701 | |||
702 | } | ||
703 | else if (current_char == 'T') | ||
704 | { | ||
705 | /* | ||
706 | Current track information | ||
707 | */ | ||
708 | current_token++; | ||
709 | |||
710 | current_char = toupper(*current_token); | ||
711 | |||
712 | struct mp3entry* id3 = rb->audio_current_track(); | ||
713 | |||
714 | int elapsed_length = id3->elapsed / 1000; | ||
715 | int track_length = id3->length / 1000; | ||
716 | int track_remaining = track_length - elapsed_length; | ||
717 | |||
718 | if (current_char == 'E') | ||
719 | { | ||
720 | rb->talk_value(elapsed_length, UNIT_TIME, true); | ||
721 | } | ||
722 | else if (current_char == 'L') | ||
723 | { | ||
724 | rb->talk_value(track_length, UNIT_TIME, true); | ||
725 | } | ||
726 | else if (current_char == 'R') | ||
727 | { | ||
728 | rb->talk_value(track_remaining, UNIT_TIME, true); | ||
729 | } | ||
730 | else if (current_char == 'T' && id3->title) | ||
731 | { | ||
732 | rb->talk_spell(id3->title, true); | ||
733 | } | ||
734 | else if (current_char == 'A' && id3->albumartist) | ||
735 | { | ||
736 | rb->talk_spell(id3->albumartist, true); | ||
737 | } | ||
738 | /* prefix suffix connectives */ | ||
739 | else if (current_char == '1') | ||
740 | { | ||
741 | rb->talk_id(LANG_ELAPSED, true); | ||
742 | } | ||
743 | else if (current_char == '2') | ||
744 | { | ||
745 | rb->talk_id(LANG_REMAIN, true); | ||
746 | } | ||
747 | else if (current_char == '3') | ||
748 | { | ||
749 | rb->talk_id(LANG_OF, true); | ||
750 | } | ||
751 | } | ||
752 | else if (current_char == 'P') | ||
753 | { | ||
754 | /* | ||
755 | Current playlist information | ||
756 | */ | ||
757 | current_token++; | ||
758 | |||
759 | current_char = toupper(*current_token); | ||
760 | struct playlist_info *pl; | ||
761 | int current_track = 0; | ||
762 | int remaining_tracks = 0; | ||
763 | int total_tracks = rb->playlist_amount(); | ||
764 | |||
765 | if (!isdigit(current_char)) { | ||
766 | pl = rb->playlist_get_current(); | ||
767 | current_track = _playlist_get_display_index(pl); | ||
768 | remaining_tracks = total_tracks - current_track; | ||
769 | } | ||
770 | |||
771 | if (total_tracks > 0 || testing) | ||
772 | { | ||
773 | if (current_char == 'C') | ||
774 | { | ||
775 | rb->talk_number(current_track, true); | ||
776 | } | ||
777 | else if (current_char == 'N') | ||
778 | { | ||
779 | rb->talk_number(total_tracks, true); | ||
780 | } | ||
781 | else if (current_char == 'R') | ||
782 | { | ||
783 | rb->talk_number(remaining_tracks, true); | ||
784 | } | ||
785 | /* prefix suffix connectives */ | ||
786 | else if (current_char == '1') | ||
787 | { | ||
788 | rb->talk_id(LANG_TRACK, true); | ||
789 | } | ||
790 | else if (current_char == '2') | ||
791 | { | ||
792 | rb->talk_id(LANG_OF, true); | ||
793 | } | ||
794 | } | ||
795 | else if (total_tracks == 0) | ||
796 | skip_next_group = true; | ||
797 | } | ||
798 | else if (current_char == 'B') | ||
799 | { | ||
800 | /* | ||
801 | Battery | ||
802 | */ | ||
803 | current_token++; | ||
804 | |||
805 | current_char = toupper(*current_token); | ||
806 | |||
807 | if (current_char == 'P') | ||
808 | { | ||
809 | rb->talk_value(rb->battery_level(), UNIT_PERCENT, true); | ||
810 | } | ||
811 | else if (current_char == 'M') | ||
812 | { | ||
813 | rb->talk_value(rb->battery_time() * 60, UNIT_TIME, true); | ||
814 | } | ||
815 | /* prefix suffix connectives */ | ||
816 | else if (current_char == '1') | ||
817 | { | ||
818 | rb->talk_id(LANG_BATTERY_TIME, true); | ||
819 | } | ||
820 | } | ||
821 | else if (current_char == ' ') | ||
822 | { | ||
823 | /* | ||
824 | Catch your breath | ||
825 | */ | ||
826 | rb->talk_id(VOICE_PAUSE, true); | ||
827 | } | ||
828 | else if (current_char == GROUPING_CHAR && gAnnounce.grouping > 0) | ||
829 | { | ||
830 | gAnnounce.count++; | ||
831 | |||
832 | if (gAnnounce.count >= gAnnounce.grouping && !testing && !skip_next_group) | ||
833 | break; | ||
834 | |||
835 | skip_next_group = false; | ||
836 | |||
837 | } | ||
838 | current_token++; | ||
839 | } | ||
840 | |||
841 | return current_token; | ||
842 | } | ||