@@ 132,11 132,20 @@ void speak(char *ssml, char *mark, char* fallback) {
int flags = espeakCHARS_AUTO | espeakPHONEMES | espeakENDPAUSE | espeakCHARS_UTF8 | espeakSSML;
if (mark != NULL && c_ssmlHasMark(mark, ssml))
espeak_Synth_Mark(ssml, strlen(ssml)+1, mark, 0, flags, NULL, NULL);
- else if (fallback != NULL)
+ else if (fallback != NULL && c_ssmlHasMark(fallback, ssml))
espeak_Synth_Mark(ssml, strlen(ssml)+1, fallback, 0, flags, NULL, NULL);
else espeak_Synth(ssml, strlen(ssml)+1, 0, POS_CHARACTER, 0, flags, NULL, NULL);
}
+void speak_text(char *text, espeak_PARAMETER param, int value) {
+ if (param != 0) espeak_SetParameter(param, value, /* relative */1);
+
+ int flags = espeakCHARS_AUTO | espeakPHONEMES | espeakENDPAUSE | espeakCHARS_UTF8;
+ espeak_Synth(text, strlen(text)+1, 0, POS_CHARACTER, 0, flags, NULL, NULL);
+
+ if (param != 0) espeak_SetParameter(param, espeak_GetParameter(param, /* current */0), /* relative */0);
+}
+
/* Utilities */
#define SWAP(type, a, b) {type temp = a; a = b; b = temp;}
int min(int a, int b) {return a < b ? a : b;}
@@ 149,8 158,8 @@ int levenshtein_distance(const char *a, const char* b) {
SWAP(int, a_len, b_len);
SWAP(const char*, a, b);
}
- int *v0 = malloc(sizeof(int)*b_len);
- int *v1 = malloc(sizeof(int)*b_len);
+ int *v0 = malloc(sizeof(int)*(b_len+1));
+ int *v1 = malloc(sizeof(int)*(b_len+1));
// Initialize v0 for an edit distance of an empty a
for (int i = 0; i < b_len; i++) v0[i] = i;
@@ 160,7 169,7 @@ int levenshtein_distance(const char *a, const char* b) {
// Edit distance from empty string is delete i characters.
v1[0] = i;
- for (int j = 0; j < b_len - 1; j++) {
+ for (int j = 0; j < b_len; j++) {
int deletion_cost = v0[j+1] + 1; // above cell + deletion
int insertion_cost = v1[j] + 1; // left cell + insertion
// top left cell + maybe substitution
@@ 172,39 181,50 @@ int levenshtein_distance(const char *a, const char* b) {
// Progress to next row
SWAP(int*, v0, v1);
}
- return v0[b_len-1];
+
+ int ret = v0[b_len];
+ free(v0); free(v1);
+ return ret;
}
int MAX_DIST = 3; // Is this ideal?
char *select_link(char **links, char *command) {
// Pass 1, min distance
int score = INT_MAX;
- for (int i = 0; strcmp(links[i], " ") != 0; i++)
+ for (int i = 0; strcmp(links[i], " ") != 0; i++) {
score = min(score, levenshtein_distance(command, links[i]));
+ }
// Pass 2: Is ambiguous?
- int is_ambiguous = 0;
+ int num_matches = 0;
for (int i = 0; strcmp(links[i], " ") != 0; i++)
- is_ambiguous |= score >= levenshtein_distance(command, links[i]) - MAX_DIST;
+ if (score >= levenshtein_distance(command, links[i]) - MAX_DIST) num_matches++;
+
+ espeak_Cancel();
// Pass 3: Retrieve answer
for (int i = 0; strcmp(links[i], " ") != 0; i += 3) {
if (score < levenshtein_distance(command, links[i]) &&
score < levenshtein_distance(command, links[i+1]) &&
score < levenshtein_distance(command, links[i+2])) continue;
- if (!is_ambiguous) return links[i+2];
+ if (num_matches == 1) return links[i+2];
- // TODO Apply formatting!
- speak(links[i+2], NULL, NULL); speak(links[i], NULL, NULL); speak(links[i+1], NULL, NULL);
+ // Communicate
+ printf("%s\t%s\t%s\n", links[i+2], links[i], links[i+1]);
+ speak_text(links[i+2], espeakRATE, 20); // URL, fast
+ speak_text(links[i], espeakPITCH, -40); // Label, low
+ speak_text(links[i+1], espeakVOLUME, -40); // Title, quite
}
- return "";
+ return NULL;
}
struct termios stored_settings, no_echo;
int read_keyboard = 0;
-int speak_finalize(char *ssml) {
+int speak_finalize(char *ssml, char **links, char **out_link) {
while (read_keyboard) {
+ if (out_link != NULL && *out_link != NULL) return 0;
+
if (getc(stdin) == '\033') {
char mark[200];
char fallback[200];
@@ 272,9 292,13 @@ int speak_finalize(char *ssml) {
tcsetattr(0, TCSANOW, &stored_settings);
char *line = NULL;
size_t len = 0;
- if (getline(&line, &len, stdin) < 0) {
+ if (getline(&line, &len, stdin) < 0)
fprintf(stderr, "Failed to read stdin line!\n");
- } else printf("%s\n", line); // TODO Do something with this!
+ else if (*line == '\n') {
+ espeak_Cancel();
+ return 0;
+ } else if (out_link != NULL)
+ *out_link = select_link(links, line);
tcsetattr(0, TCSANOW, &no_echo);
}
}
@@ 347,8 371,8 @@ int main(int argc, char **argv) {
no_echo = stored_settings;
no_echo.c_lflag &= (~ICANON);
no_echo.c_lflag &= (~ECHO);
- no_echo.c_cc[VTIME] = 1;
- no_echo.c_cc[VMIN] = 0;
+ no_echo.c_cc[VTIME] = 0;
+ no_echo.c_cc[VMIN] = 1;
tcsetattr(0, TCSANOW, &no_echo);
break;
case 'w':
@@ 391,23 415,35 @@ int main(int argc, char **argv) {
#endif
use_espeak = 1;
+#if 0 // Unit-test levenshtein_distance
+ int dist = levenshtein_distance("kitten", "kitten");
+ printf("kitten => kitten: %i\n", dist);
+ if (dist != 0) return 10;
+ dist = levenshtein_distance("kitten", "sitting");
+ printf("kitten => sitting: %i\n", dist);
+ if (dist != 3) return 11;
+#endif
+
struct session *session = c_newSession();
struct page *referer = c_initialReferer();
if (use_espeak) speak_err = speak_initialize();
- char *ssml;
+ char *ssml, **links, *uri;
for (int i = optind; i < argc; i++) {
- if (use_espeak && speak_err == 0) speak(argv[i], NULL, NULL);
+ uri = argv[i];
+
+read_uri:
+ if (use_espeak && speak_err == 0) speak_text(uri, espeakRATE, 10);
#ifdef WITH_SPEECHD
- else if (spd_conn != NULL) spd_say(spd_conn, SPD_MESSAGE, argv[i]);
+ else if (spd_conn != NULL) spd_say(spd_conn, SPD_MESSAGE, uri);
#endif
else printf("%s\n", argv[i]);
if (logpath != NULL) c_enableLogging(session);
- struct page *page = c_fetchURL(session, mimes, referer, argv[i]);
+ struct page *page = c_fetchURL(session, mimes, referer, uri);
ssml = c_renderDoc(session, page, use_espeak);
- char **links = c_extractLinks(page);
+ links = c_extractLinks(page);
if (logpath != NULL) c_writeLog(logpath, session);
@@ 420,7 456,11 @@ int main(int argc, char **argv) {
c_freePage(page);
}
- if (use_espeak & speak_err == 0) speak_err = speak_finalize(ssml);
+
+ uri = NULL;
+ if (use_espeak & speak_err == 0) speak_err = speak_finalize(ssml, links, &uri);
+ if (uri != NULL) goto read_uri;
+
#ifdef WITH_SPEECHD
if (spd_conn != NULL) spd_close(spd_conn);
#endif