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
This commit is contained in:
parent
3f828e9244
commit
be04c4be0a
5 changed files with 1055 additions and 1 deletions
|
@ -15904,3 +15904,209 @@
|
|||
xduoox20,xduoox3,xduoox3ii: "Double tap HOME to cancel."
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_DATE
|
||||
desc: for constructing time and date announcements
|
||||
user: core
|
||||
<source>
|
||||
*: "Date"
|
||||
</source>
|
||||
<dest>
|
||||
*: "Date"
|
||||
</dest>
|
||||
<voice>
|
||||
*: "Date"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_CLEAR_ALL
|
||||
desc:
|
||||
user: core
|
||||
<source>
|
||||
*: "Clear all"
|
||||
</source>
|
||||
<dest>
|
||||
*: "Clear all"
|
||||
</dest>
|
||||
<voice>
|
||||
*: "Clear all"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_CANCEL_0
|
||||
desc: CANCEL.
|
||||
user: core
|
||||
<source>
|
||||
*: "Cancel"
|
||||
</source>
|
||||
<dest>
|
||||
*: "Cancel"
|
||||
</dest>
|
||||
<voice>
|
||||
*: "Cancel"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_SAVE
|
||||
desc:
|
||||
user: core
|
||||
<source>
|
||||
*: "Save"
|
||||
</source>
|
||||
<dest>
|
||||
*: "Save"
|
||||
</dest>
|
||||
<voice>
|
||||
*: "Save"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_TIMEOUT
|
||||
desc:
|
||||
user: core
|
||||
<source>
|
||||
*: "Timeout"
|
||||
</source>
|
||||
<dest>
|
||||
*: "Timeout"
|
||||
</dest>
|
||||
<voice>
|
||||
*: "Timeout"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_TRACK
|
||||
desc: used in track x of y constructs
|
||||
user: core
|
||||
<source>
|
||||
*: none
|
||||
hotkey: "Track"
|
||||
</source>
|
||||
<dest>
|
||||
*: none
|
||||
hotkey: "Track"
|
||||
</dest>
|
||||
<voice>
|
||||
*: none
|
||||
hotkey: "Track"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_ELAPSED
|
||||
desc: prefix for elapsed playtime announcement
|
||||
user: core
|
||||
<source>
|
||||
*: none
|
||||
hotkey: "Elapsed"
|
||||
</source>
|
||||
<dest>
|
||||
*: none
|
||||
hotkey: "Elapsed"
|
||||
</dest>
|
||||
<voice>
|
||||
*: none
|
||||
hotkey: "Elapsed"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_ANNOUNCEMENT_FMT
|
||||
desc: format for wps hotkey announcement
|
||||
user: core
|
||||
<source>
|
||||
*: none
|
||||
hotkey: "Announcement format"
|
||||
</source>
|
||||
<dest>
|
||||
*: none
|
||||
hotkey: "Announcement format"
|
||||
</dest>
|
||||
<voice>
|
||||
*: none
|
||||
hotkey: "Announcement format"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_REMAIN
|
||||
desc: for constructs such as number of tracks remaining etc
|
||||
user: core
|
||||
<source>
|
||||
*: none
|
||||
hotkey: "Remain"
|
||||
</source>
|
||||
<dest>
|
||||
*: none
|
||||
hotkey: "Remain"
|
||||
</dest>
|
||||
<voice>
|
||||
*: none
|
||||
hotkey: "Remain"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_GROUPING
|
||||
desc:
|
||||
user: core
|
||||
<source>
|
||||
*: none
|
||||
hotkey: "Grouping"
|
||||
</source>
|
||||
<dest>
|
||||
*: none
|
||||
hotkey: "Grouping"
|
||||
</dest>
|
||||
<voice>
|
||||
*: none
|
||||
hotkey: "Grouping"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_ANNOUNCE_ON
|
||||
desc:
|
||||
user: core
|
||||
<source>
|
||||
*: none
|
||||
hotkey: "Announce on"
|
||||
</source>
|
||||
<dest>
|
||||
*: none
|
||||
hotkey: "Announce on"
|
||||
</dest>
|
||||
<voice>
|
||||
*: none
|
||||
hotkey: "Announce on"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_TRACK_CHANGE
|
||||
desc:
|
||||
user: core
|
||||
<source>
|
||||
*: none
|
||||
hotkey: "Track change"
|
||||
</source>
|
||||
<dest>
|
||||
*: none
|
||||
hotkey: "Track change"
|
||||
</dest>
|
||||
<voice>
|
||||
*: none
|
||||
hotkey: "Track change"
|
||||
</voice>
|
||||
</phrase>
|
||||
<phrase>
|
||||
id: LANG_HOLD_FOR_SETTINGS
|
||||
desc:
|
||||
user: core
|
||||
<source>
|
||||
*: none
|
||||
hotkey: "Hold for settings"
|
||||
</source>
|
||||
<dest>
|
||||
*: none
|
||||
hotkey: "Hold for settings"
|
||||
</dest>
|
||||
<voice>
|
||||
*: none
|
||||
hotkey: "Hold for settings"
|
||||
</voice>
|
||||
</phrase>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
2048,games
|
||||
alpine_cdc,apps
|
||||
alarmclock,apps
|
||||
announce_status,demos
|
||||
autostart,apps
|
||||
battery_bench,apps
|
||||
bench_scaler,apps
|
||||
|
|
|
@ -151,6 +151,11 @@ starfield.c
|
|||
vu_meter.c
|
||||
wormlet.c
|
||||
|
||||
#ifdef HAVE_HOTKEY
|
||||
announce_status.c
|
||||
#endif
|
||||
|
||||
|
||||
/* Plugins needing the grayscale lib on low-depth LCDs */
|
||||
fire.c
|
||||
plasma.c
|
||||
|
|
842
apps/plugins/announce_status.c
Normal file
842
apps/plugins/announce_status.c
Normal file
|
@ -0,0 +1,842 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2003-2005 Jörg Hohensohn
|
||||
* Copyright (C) 2020 BILGUS
|
||||
*
|
||||
*
|
||||
*
|
||||
* Usage: Start plugin, it will stay in the background.
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "plugin.h"
|
||||
#include "lib/kbd_helper.h"
|
||||
#include "lib/configfile.h"
|
||||
|
||||
/****************** constants ******************/
|
||||
#define MAX_GROUPS 7
|
||||
#define MAX_ANNOUNCE_WPS 63
|
||||
#define ANNOUNCEMENT_TIMEOUT 10
|
||||
#define GROUPING_CHAR ';'
|
||||
|
||||
#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFF)
|
||||
#define EV_OTHINSTANCE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFE)
|
||||
#define EV_STARTUP MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x01)
|
||||
#define EV_TRACKCHANGE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x02)
|
||||
|
||||
#define CFG_FILE "/VoiceTSR.cfg"
|
||||
#define CFG_VER 1
|
||||
|
||||
#if CONFIG_RTC
|
||||
#define K_TIME "DT D1;\n\n"
|
||||
#define K_DATE "DD D2;\n\n"
|
||||
#else
|
||||
#define K_TIME ""
|
||||
#define K_DATE ""
|
||||
#endif
|
||||
|
||||
#define K_TRACK_TA "TT TA;\n"
|
||||
#define K_TRACK "TE TL TR;\n"
|
||||
#define K_TRACK1 "T1 T2 T3;\n\n"
|
||||
#define K_PLAYLIST "PC PN PR P1 P2;\n"
|
||||
#define K_BATTERY "BP BM B1;\n"
|
||||
#define K_SLEEP "RS R2 R3;\n"
|
||||
#define K_RUNTIME "RT R1;"
|
||||
#define KEYBD_LAYOUT (K_TIME K_DATE K_TRACK_TA K_TRACK K_TRACK1 K_PLAYLIST K_BATTERY K_SLEEP K_RUNTIME)
|
||||
|
||||
/****************** prototypes ******************/
|
||||
void print_scroll(char* string); /* implements a scrolling screen */
|
||||
|
||||
int get_playtime(void); /* return the current track time in seconds */
|
||||
int get_tracklength(void); /* return the total length of the current track */
|
||||
int get_track(void); /* return the track number */
|
||||
void get_playmsg(void); /* update the play message with Rockbox info */
|
||||
|
||||
void thread_create(void);
|
||||
void thread(void); /* the thread running it all */
|
||||
void thread_quit(void);
|
||||
static int voice_general_info(bool testing);
|
||||
static unsigned char* voice_info_group(unsigned char* current_token, bool testing);
|
||||
|
||||
int main(const void* parameter); /* main loop */
|
||||
enum plugin_status plugin_start(const void* parameter); /* entry */
|
||||
|
||||
|
||||
/****************** data types ******************/
|
||||
|
||||
/****************** globals ******************/
|
||||
/* communication to the worker thread */
|
||||
static struct
|
||||
{
|
||||
bool exiting; /* signal to the thread that we want to exit */
|
||||
unsigned int id; /* worker thread id */
|
||||
struct event_queue queue; /* thread event queue */
|
||||
long *stack;
|
||||
ssize_t stacksize;
|
||||
void *buf;
|
||||
size_t buf_size;
|
||||
|
||||
} gThread;
|
||||
|
||||
static struct
|
||||
{
|
||||
int interval;
|
||||
int announce_on;
|
||||
int grouping;
|
||||
|
||||
int timeout;
|
||||
int count;
|
||||
unsigned int index;
|
||||
int bin_added;
|
||||
|
||||
unsigned char wps_fmt[MAX_ANNOUNCE_WPS+1];
|
||||
} gAnnounce;
|
||||
|
||||
static struct configdata config[] =
|
||||
{
|
||||
{TYPE_INT, 0, 10000, { .int_p = &gAnnounce.interval }, "Interval", NULL},
|
||||
{TYPE_INT, 0, 2, { .int_p = &gAnnounce.announce_on }, "Announce", NULL},
|
||||
{TYPE_INT, 0, 10, { .int_p = &gAnnounce.grouping }, "Grouping", NULL},
|
||||
{TYPE_INT, 0, 10000, { .int_p = &gAnnounce.bin_added }, "Added", NULL},
|
||||
{TYPE_STRING, 0, MAX_ANNOUNCE_WPS+1,
|
||||
{ .string = (char*)&gAnnounce.wps_fmt }, "Fmt", NULL},
|
||||
};
|
||||
|
||||
const int gCfg_sz = sizeof(config)/sizeof(*config);
|
||||
/****************** communication with Rockbox playback ******************/
|
||||
|
||||
#if 0
|
||||
/* return the track number */
|
||||
int get_track(void)
|
||||
{
|
||||
//if (rb->audio_status() == (AUDIO_STATUS_PLAY | AUDIO_STATUS_PAUSE))
|
||||
struct mp3entry* p_mp3entry;
|
||||
|
||||
p_mp3entry = rb->audio_current_track();
|
||||
if (p_mp3entry == NULL)
|
||||
return 0;
|
||||
|
||||
return p_mp3entry->index + 1; /* track numbers start with 1 */
|
||||
}
|
||||
#endif
|
||||
|
||||
static void playback_event_callback(unsigned short id, void *data)
|
||||
{
|
||||
(void)id;
|
||||
(void)data;
|
||||
rb->queue_post(&gThread.queue, EV_TRACKCHANGE, 0);
|
||||
}
|
||||
|
||||
/****************** config functions *****************/
|
||||
static void config_set_defaults(void)
|
||||
{
|
||||
gAnnounce.bin_added = 0;
|
||||
gAnnounce.interval = ANNOUNCEMENT_TIMEOUT;
|
||||
gAnnounce.announce_on = 0;
|
||||
gAnnounce.grouping = 0;
|
||||
gAnnounce.wps_fmt[0] = '\0';
|
||||
}
|
||||
|
||||
static void config_reset_voice(void)
|
||||
{
|
||||
/* don't want to change these so save a copy */
|
||||
int interval = gAnnounce.interval;
|
||||
int announce = gAnnounce.announce_on;
|
||||
int grouping = gAnnounce.grouping;
|
||||
|
||||
if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0)
|
||||
{
|
||||
rb->splash(100, "ERROR!");
|
||||
return;
|
||||
}
|
||||
|
||||
/* restore other settings */
|
||||
gAnnounce.interval = interval;
|
||||
gAnnounce.announce_on = announce;
|
||||
gAnnounce.grouping = grouping;
|
||||
}
|
||||
|
||||
/****************** helper fuctions ******************/
|
||||
|
||||
void announce(void)
|
||||
{
|
||||
rb->talk_force_shutup();
|
||||
rb->sleep(HZ / 2);
|
||||
voice_general_info(false);
|
||||
//rb->talk_force_enqueue_next();
|
||||
}
|
||||
|
||||
static void announce_test(void)
|
||||
{
|
||||
rb->talk_force_shutup();
|
||||
rb->sleep(HZ / 2);
|
||||
voice_info_group(gAnnounce.wps_fmt, true);
|
||||
|
||||
//rb->talk_force_enqueue_next();
|
||||
}
|
||||
|
||||
static void announce_add(const char *str)
|
||||
{
|
||||
int len_cur = rb->strlen(gAnnounce.wps_fmt);
|
||||
int len_str = rb->strlen(str);
|
||||
if (len_cur + len_str > MAX_ANNOUNCE_WPS)
|
||||
return;
|
||||
rb->strcpy(&gAnnounce.wps_fmt[len_cur], str);
|
||||
announce_test();
|
||||
|
||||
}
|
||||
|
||||
static int _playlist_get_display_index(struct playlist_info *playlist)
|
||||
{
|
||||
/* equivalent of the function found in playlist.c */
|
||||
if(!playlist)
|
||||
return -1;
|
||||
/* first_index should always be index 0 for display purposes */
|
||||
int index = playlist->index;
|
||||
index -= playlist->first_index;
|
||||
if (index < 0)
|
||||
index += playlist->amount;
|
||||
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
static enum themable_icons icon_callback(int selected_item, void * data)
|
||||
{
|
||||
(void)data;
|
||||
|
||||
if(selected_item < MAX_GROUPS && selected_item >= 0)
|
||||
{
|
||||
int bin = 1 << (selected_item);
|
||||
if ((gAnnounce.bin_added & bin) == bin)
|
||||
return Icon_Submenu;
|
||||
}
|
||||
|
||||
return Icon_NOICON;
|
||||
}
|
||||
|
||||
static int announce_menu_cb(int action,
|
||||
const struct menu_item_ex *this_item,
|
||||
struct gui_synclist *this_list)
|
||||
{
|
||||
(void)this_item;
|
||||
unsigned short *kbd_p = gThread.buf;
|
||||
size_t kbd_bufsz = gThread.buf_size;
|
||||
|
||||
int selection = rb->gui_synclist_get_sel_pos(this_list);
|
||||
|
||||
if(action == ACTION_ENTER_MENUITEM)
|
||||
{
|
||||
rb->gui_synclist_set_icon_callback(this_list, icon_callback);
|
||||
}
|
||||
else if ((action == ACTION_STD_OK))
|
||||
{
|
||||
//rb->splashf(100, "%d", selection);
|
||||
if (selection < MAX_GROUPS && selection >= 0) /* only add premade tags once */
|
||||
{
|
||||
int bin = 1 << (selection);
|
||||
if ((gAnnounce.bin_added & bin) == bin)
|
||||
return 0;
|
||||
|
||||
gAnnounce.bin_added |= bin;
|
||||
}
|
||||
|
||||
switch(selection) {
|
||||
|
||||
case 0: /*Time*/
|
||||
announce_add("D1Dt ;");
|
||||
break;
|
||||
case 1: /*Date*/
|
||||
announce_add("D2Dd ;");
|
||||
break;
|
||||
case 2: /*Track*/
|
||||
announce_add("TT T1TeT2Tr ;");
|
||||
break;
|
||||
case 3: /*Playlist*/
|
||||
announce_add("P1PC P2PN ;");
|
||||
break;
|
||||
case 4: /*Battery*/
|
||||
announce_add("B1Bp ;");
|
||||
break;
|
||||
case 5: /*Sleep*/
|
||||
announce_add("R2RsR3 ;");
|
||||
break;
|
||||
case 6: /*Runtime*/
|
||||
announce_add("R1Rt ;");
|
||||
break;
|
||||
case 7: /* sep */
|
||||
break;
|
||||
case 8: /*Clear All*/
|
||||
gAnnounce.wps_fmt[0] = '\0';
|
||||
gAnnounce.bin_added = 0;
|
||||
rb->splash(HZ / 2, ID2P(LANG_RESET_DONE_CLEAR));
|
||||
break;
|
||||
case 9: /* inspect it */
|
||||
if (!kbd_create_layout(KEYBD_LAYOUT, kbd_p, kbd_bufsz))
|
||||
kbd_p = NULL;
|
||||
|
||||
rb->kbd_input(gAnnounce.wps_fmt, MAX_ANNOUNCE_WPS, kbd_p);
|
||||
break;
|
||||
case 10: /*test it*/
|
||||
announce_test();
|
||||
break;
|
||||
case 11: /*cancel*/
|
||||
config_reset_voice();
|
||||
return ACTION_STD_CANCEL;
|
||||
case 12: /* save */
|
||||
return ACTION_STD_CANCEL;
|
||||
default:
|
||||
return action;
|
||||
}
|
||||
rb->gui_synclist_draw(this_list); /* redraw */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
static int announce_menu(void)
|
||||
{
|
||||
int selection = 0;
|
||||
|
||||
MENUITEM_STRINGLIST(announce_menu, "Announcements", announce_menu_cb,
|
||||
ID2P(LANG_TIME),
|
||||
ID2P(LANG_DATE),
|
||||
ID2P(LANG_TRACK),
|
||||
ID2P(LANG_PLAYLIST),
|
||||
ID2P(LANG_BATTERY_MENU),
|
||||
ID2P(LANG_SLEEP_TIMER),
|
||||
ID2P(LANG_RUNNING_TIME),
|
||||
ID2P(VOICE_BLANK),
|
||||
ID2P(LANG_CLEAR_ALL),
|
||||
ID2P(LANG_ANNOUNCEMENT_FMT),
|
||||
ID2P(LANG_VOICE),
|
||||
ID2P(LANG_CANCEL_0),
|
||||
ID2P(LANG_SAVE));
|
||||
|
||||
selection = rb->do_menu(&announce_menu, &selection, NULL, true);
|
||||
if (selection == MENU_ATTACHED_USB)
|
||||
return PLUGIN_USB_CONNECTED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Shows the settings menu
|
||||
*/
|
||||
static int settings_menu(void)
|
||||
{
|
||||
int selection = 0;
|
||||
//bool old_val;
|
||||
|
||||
MENUITEM_STRINGLIST(settings_menu, "Announce Settings", NULL,
|
||||
ID2P(LANG_TIMEOUT),
|
||||
ID2P(LANG_ANNOUNCE_ON),
|
||||
ID2P(LANG_GROUPING),
|
||||
ID2P(LANG_ANNOUNCEMENT_FMT),
|
||||
ID2P(VOICE_BLANK),
|
||||
ID2P(LANG_MENU_QUIT),
|
||||
ID2P(LANG_SAVE_EXIT));
|
||||
|
||||
static const struct opt_items announce_options[] = {
|
||||
{ STR(LANG_OFF)},
|
||||
{ STR(LANG_TRACK_CHANGE)},
|
||||
};
|
||||
|
||||
do {
|
||||
selection=rb->do_menu(&settings_menu,&selection, NULL, true);
|
||||
switch(selection) {
|
||||
|
||||
case 0:
|
||||
rb->set_int(rb->str(LANG_TIMEOUT), "s", UNIT_SEC,
|
||||
&gAnnounce.interval, NULL, 1, 1, 360, NULL );
|
||||
break;
|
||||
case 1:
|
||||
rb->set_option(rb->str(LANG_ANNOUNCE_ON),
|
||||
&gAnnounce.announce_on, INT, announce_options, 2, NULL);
|
||||
break;
|
||||
case 2:
|
||||
rb->set_int(rb->str(LANG_GROUPING), "", 1,
|
||||
&gAnnounce.grouping, NULL, 1, 0, 7, NULL );
|
||||
break;
|
||||
case 3:
|
||||
announce_menu();
|
||||
break;
|
||||
case 4: /*sep*/
|
||||
continue;
|
||||
case 5:
|
||||
return -1;
|
||||
break;
|
||||
case 6:
|
||||
configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case MENU_ATTACHED_USB:
|
||||
return PLUGIN_USB_CONNECTED;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
} while ( selection >= 0 );
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/****************** main thread + helper ******************/
|
||||
void thread(void)
|
||||
{
|
||||
long interval;
|
||||
long last_tick = *rb->current_tick; /* for 1 sec tick */
|
||||
|
||||
struct queue_event ev;
|
||||
while (!gThread.exiting)
|
||||
{
|
||||
rb->queue_wait(&gThread.queue, &ev);
|
||||
interval = gAnnounce.interval * HZ;
|
||||
switch (ev.id)
|
||||
{
|
||||
case EV_EXIT:
|
||||
return;
|
||||
case EV_OTHINSTANCE:
|
||||
if (*rb->current_tick - last_tick >= interval)
|
||||
{
|
||||
last_tick += interval;
|
||||
rb->sleep(0);
|
||||
announce();
|
||||
}
|
||||
break;
|
||||
case EV_STARTUP:
|
||||
rb->beep_play(1500, 100, 1000);
|
||||
break;
|
||||
case EV_TRACKCHANGE:
|
||||
rb->sleep(0);
|
||||
announce();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void plugin_buffer_init(void)
|
||||
{
|
||||
if (gThread.buf == 0)
|
||||
{
|
||||
rb->memset(&gThread, 0, sizeof(gThread));
|
||||
gThread.buf = rb->plugin_get_buffer(&gThread.buf_size);
|
||||
ALIGN_BUFFER(gThread.buf, gThread.buf_size, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void thread_create(void)
|
||||
{
|
||||
/* init the worker thread */
|
||||
gThread.stacksize = gThread.buf_size;
|
||||
gThread.buf_size -= gThread.stacksize;
|
||||
|
||||
gThread.stack = (long *) gThread.buf + gThread.buf_size;
|
||||
|
||||
ALIGN_BUFFER(gThread.stack, gThread.stacksize, 4);
|
||||
|
||||
if (gThread.stacksize < DEFAULT_STACK_SIZE)
|
||||
{
|
||||
rb->splash(HZ*2, "Out of memory");
|
||||
gThread.exiting = true;
|
||||
gThread.id = UINT_MAX;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* put the thread's queue in the bcast list */
|
||||
rb->queue_init(&gThread.queue, true);
|
||||
|
||||
gThread.id = rb->create_thread(thread, gThread.stack, gThread.stacksize,
|
||||
0, "vTSR"
|
||||
IF_PRIO(, PRIORITY_BACKGROUND)
|
||||
IF_COP(, CPU));
|
||||
rb->queue_post(&gThread.queue, EV_STARTUP, 0);
|
||||
rb->yield();
|
||||
|
||||
}
|
||||
|
||||
void thread_quit(void)
|
||||
{
|
||||
if (!gThread.exiting) {
|
||||
rb->queue_post(&gThread.queue, EV_EXIT, 0);
|
||||
rb->thread_wait(gThread.id);
|
||||
/* remove the thread's queue from the broadcast list */
|
||||
rb->queue_delete(&gThread.queue);
|
||||
gThread.exiting = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* callback to end the TSR plugin, called before a new one gets loaded */
|
||||
static bool exit_tsr(bool reenter)
|
||||
{
|
||||
if (reenter)
|
||||
{
|
||||
rb->queue_post(&gThread.queue, EV_OTHINSTANCE, 0);
|
||||
return false; /* dont let it start again */
|
||||
}
|
||||
thread_quit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/****************** main ******************/
|
||||
|
||||
int plugin_main(const void* parameter)
|
||||
{
|
||||
(void)parameter;
|
||||
bool settings = false;
|
||||
int i = 0;
|
||||
|
||||
gAnnounce.index = 0;
|
||||
gAnnounce.timeout = 0;
|
||||
|
||||
rb->talk_id(LANG_HOLD_FOR_SETTINGS, false);
|
||||
rb->splash(HZ / 2, "Announce Status");
|
||||
|
||||
if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0)
|
||||
{
|
||||
/* If the loading failed, save a new config file */
|
||||
config_set_defaults();
|
||||
configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
|
||||
|
||||
rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS));
|
||||
}
|
||||
|
||||
rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS));
|
||||
|
||||
rb->button_clear_queue();
|
||||
if (rb->button_get_w_tmo(HZ) > BUTTON_NONE)
|
||||
{
|
||||
while ((rb->button_get(false) & BUTTON_REL) != BUTTON_REL)
|
||||
{
|
||||
if (i & 1)
|
||||
rb->beep_play(800, 100, 1000);
|
||||
|
||||
if (++i > 15)
|
||||
{
|
||||
settings = true;
|
||||
break;
|
||||
}
|
||||
sleep(HZ / 5);
|
||||
}
|
||||
}
|
||||
|
||||
plugin_buffer_init(); /* need buffer for custom keyboard layout */
|
||||
|
||||
if (settings)
|
||||
{
|
||||
rb->splash(100, ID2P(LANG_SETTINGS));
|
||||
int ret = settings_menu();
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
gAnnounce.timeout = *rb->current_tick;
|
||||
rb->plugin_tsr(exit_tsr); /* stay resident */
|
||||
|
||||
if (gAnnounce.announce_on == 1)
|
||||
rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, playback_event_callback);
|
||||
|
||||
thread_create();
|
||||
#ifdef DEBUG
|
||||
return rb->default_event_handler(button);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/***************** Plugin Entry Point *****************/
|
||||
|
||||
|
||||
enum plugin_status plugin_start(const void* parameter)
|
||||
{
|
||||
/* now go ahead and have fun! */
|
||||
int ret = plugin_main(parameter);
|
||||
|
||||
rb->remove_event(PLAYBACK_EVENT_START_PLAYBACK, playback_event_callback);
|
||||
return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR;
|
||||
}
|
||||
|
||||
static int voice_general_info(bool testing)
|
||||
{
|
||||
unsigned char* infotemplate = gAnnounce.wps_fmt;
|
||||
|
||||
if (gAnnounce.index >= rb->strlen(gAnnounce.wps_fmt))
|
||||
gAnnounce.index = 0;
|
||||
|
||||
long current_tick = *rb->current_tick;
|
||||
|
||||
if (*infotemplate == 0)
|
||||
{
|
||||
#if CONFIG_RTC
|
||||
/* announce the time */
|
||||
voice_info_group("D1Dt ", false);
|
||||
#else
|
||||
/* announce elapsed play for this track */
|
||||
voice_info_group("T1Te ", false);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (TIME_BEFORE(current_tick, gAnnounce.timeout))
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
|
||||
gAnnounce.timeout = current_tick + gAnnounce.interval * HZ;
|
||||
|
||||
rb->talk_shutup();
|
||||
|
||||
gAnnounce.count = 0;
|
||||
infotemplate = voice_info_group(&infotemplate[gAnnounce.index], testing);
|
||||
gAnnounce.index = (infotemplate - gAnnounce.wps_fmt) + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned char* voice_info_group(unsigned char* current_token, bool testing)
|
||||
{
|
||||
unsigned char current_char;
|
||||
bool skip_next_group = false;
|
||||
gAnnounce.count = 0;
|
||||
|
||||
while (*current_token != 0)
|
||||
{
|
||||
//rb->splash(10, current_token);
|
||||
current_char = toupper(*current_token);
|
||||
if (current_char == 'D')
|
||||
{
|
||||
/*
|
||||
Date and time functions
|
||||
*/
|
||||
current_token++;
|
||||
|
||||
current_char = toupper(*current_token);
|
||||
|
||||
#if CONFIG_RTC
|
||||
struct tm *tm = rb->get_time();
|
||||
|
||||
if (true) //(valid_time(tm))
|
||||
{
|
||||
if (current_char == 'T')
|
||||
{
|
||||
rb->talk_time(tm, true);
|
||||
}
|
||||
else if (current_char == 'D')
|
||||
{
|
||||
rb->talk_date(tm, true);
|
||||
}
|
||||
/* prefix suffix connectives */
|
||||
else if (current_char == '1')
|
||||
{
|
||||
rb->talk_id(LANG_TIME, true);
|
||||
}
|
||||
else if (current_char == '2')
|
||||
{
|
||||
rb->talk_id(LANG_DATE, true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (current_char == 'R')
|
||||
{
|
||||
/*
|
||||
Sleep timer and runtime
|
||||
*/
|
||||
int sleep_remaining = sleep_remaining = rb->get_sleep_timer();
|
||||
int runtime;
|
||||
|
||||
current_token++;
|
||||
current_char = toupper(*current_token);
|
||||
if (current_char == 'T')
|
||||
{
|
||||
runtime = rb->global_status->runtime;
|
||||
rb->talk_value(runtime, UNIT_TIME, true);
|
||||
}
|
||||
/* prefix suffix connectives */
|
||||
else if (current_char == '1')
|
||||
{
|
||||
rb->talk_id(LANG_RUNNING_TIME, true);
|
||||
}
|
||||
else if (testing || sleep_remaining > 0)
|
||||
{
|
||||
if (current_char == 'S')
|
||||
{
|
||||
rb->talk_value(sleep_remaining, UNIT_TIME, true);
|
||||
}
|
||||
/* prefix suffix connectives */
|
||||
else if (current_char == '2')
|
||||
{
|
||||
rb->talk_id(LANG_SLEEP_TIMER, true);
|
||||
}
|
||||
else if (current_char == '3')
|
||||
{
|
||||
rb->talk_id(LANG_REMAIN, true);
|
||||
}
|
||||
}
|
||||
else if (sleep_remaining == 0)
|
||||
{
|
||||
skip_next_group = true;
|
||||
}
|
||||
|
||||
}
|
||||
else if (current_char == 'T')
|
||||
{
|
||||
/*
|
||||
Current track information
|
||||
*/
|
||||
current_token++;
|
||||
|
||||
current_char = toupper(*current_token);
|
||||
|
||||
struct mp3entry* id3 = rb->audio_current_track();
|
||||
|
||||
int elapsed_length = id3->elapsed / 1000;
|
||||
int track_length = id3->length / 1000;
|
||||
int track_remaining = track_length - elapsed_length;
|
||||
|
||||
if (current_char == 'E')
|
||||
{
|
||||
rb->talk_value(elapsed_length, UNIT_TIME, true);
|
||||
}
|
||||
else if (current_char == 'L')
|
||||
{
|
||||
rb->talk_value(track_length, UNIT_TIME, true);
|
||||
}
|
||||
else if (current_char == 'R')
|
||||
{
|
||||
rb->talk_value(track_remaining, UNIT_TIME, true);
|
||||
}
|
||||
else if (current_char == 'T' && id3->title)
|
||||
{
|
||||
rb->talk_spell(id3->title, true);
|
||||
}
|
||||
else if (current_char == 'A' && id3->albumartist)
|
||||
{
|
||||
rb->talk_spell(id3->albumartist, true);
|
||||
}
|
||||
/* prefix suffix connectives */
|
||||
else if (current_char == '1')
|
||||
{
|
||||
rb->talk_id(LANG_ELAPSED, true);
|
||||
}
|
||||
else if (current_char == '2')
|
||||
{
|
||||
rb->talk_id(LANG_REMAIN, true);
|
||||
}
|
||||
else if (current_char == '3')
|
||||
{
|
||||
rb->talk_id(LANG_OF, true);
|
||||
}
|
||||
}
|
||||
else if (current_char == 'P')
|
||||
{
|
||||
/*
|
||||
Current playlist information
|
||||
*/
|
||||
current_token++;
|
||||
|
||||
current_char = toupper(*current_token);
|
||||
struct playlist_info *pl;
|
||||
int current_track = 0;
|
||||
int remaining_tracks = 0;
|
||||
int total_tracks = rb->playlist_amount();
|
||||
|
||||
if (!isdigit(current_char)) {
|
||||
pl = rb->playlist_get_current();
|
||||
current_track = _playlist_get_display_index(pl);
|
||||
remaining_tracks = total_tracks - current_track;
|
||||
}
|
||||
|
||||
if (total_tracks > 0 || testing)
|
||||
{
|
||||
if (current_char == 'C')
|
||||
{
|
||||
rb->talk_number(current_track, true);
|
||||
}
|
||||
else if (current_char == 'N')
|
||||
{
|
||||
rb->talk_number(total_tracks, true);
|
||||
}
|
||||
else if (current_char == 'R')
|
||||
{
|
||||
rb->talk_number(remaining_tracks, true);
|
||||
}
|
||||
/* prefix suffix connectives */
|
||||
else if (current_char == '1')
|
||||
{
|
||||
rb->talk_id(LANG_TRACK, true);
|
||||
}
|
||||
else if (current_char == '2')
|
||||
{
|
||||
rb->talk_id(LANG_OF, true);
|
||||
}
|
||||
}
|
||||
else if (total_tracks == 0)
|
||||
skip_next_group = true;
|
||||
}
|
||||
else if (current_char == 'B')
|
||||
{
|
||||
/*
|
||||
Battery
|
||||
*/
|
||||
current_token++;
|
||||
|
||||
current_char = toupper(*current_token);
|
||||
|
||||
if (current_char == 'P')
|
||||
{
|
||||
rb->talk_value(rb->battery_level(), UNIT_PERCENT, true);
|
||||
}
|
||||
else if (current_char == 'M')
|
||||
{
|
||||
rb->talk_value(rb->battery_time() * 60, UNIT_TIME, true);
|
||||
}
|
||||
/* prefix suffix connectives */
|
||||
else if (current_char == '1')
|
||||
{
|
||||
rb->talk_id(LANG_BATTERY_TIME, true);
|
||||
}
|
||||
}
|
||||
else if (current_char == ' ')
|
||||
{
|
||||
/*
|
||||
Catch your breath
|
||||
*/
|
||||
rb->talk_id(VOICE_PAUSE, true);
|
||||
}
|
||||
else if (current_char == GROUPING_CHAR && gAnnounce.grouping > 0)
|
||||
{
|
||||
gAnnounce.count++;
|
||||
|
||||
if (gAnnounce.count >= gAnnounce.grouping && !testing && !skip_next_group)
|
||||
break;
|
||||
|
||||
skip_next_group = false;
|
||||
|
||||
}
|
||||
current_token++;
|
||||
}
|
||||
|
||||
return current_token;
|
||||
}
|
|
@ -153,7 +153,7 @@ int voicefont(FILE* voicefontids,int targetnum,char* filedir, FILE* output, unsi
|
|||
}
|
||||
else /* second run, skip the non voice only ones */
|
||||
{
|
||||
if (!voiceonly[i] == 1)
|
||||
if (!(voiceonly[i] == 1))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue