@@ 4,6 4,7 @@
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
+#include <limits.h>
#include "HsFFI.h"
#include <espeak-ng/espeak_ng.h>
@@ 135,15 136,80 @@ void speak(char *ssml, char *mark, char* fallback) {
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);
}
+
+/* Utilities */
+#define SWAP(type, a, b) {type temp = a; a = b; b = temp;}
+int min(int a, int b) {return a < b ? a : b;}
+
+/* Keyboard input */
+int levenshtein_distance(const char *a, const char* b) {
+ int a_len = strlen(a);
+ int b_len = strlen(b);
+ if (a_len < b_len) {
+ 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);
+
+ // Initialize v0 for an edit distance of an empty a
+ for (int i = 0; i < b_len; i++) v0[i] = i;
+
+ // The core algorithm
+ for (int i = 0; i < a_len; i++) {
+ // Edit distance from empty string is delete i characters.
+ v1[0] = i;
+
+ for (int j = 0; j < b_len - 1; 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
+ int substitution_cost = v0[j] + (a[i] == b[j] ? 0 : 1);
+
+ v1[j+1] = min(deletion_cost, min(insertion_cost, substitution_cost));
+ }
+
+ // Progress to next row
+ SWAP(int*, v0, v1);
+ }
+ return v0[b_len-1];
+}
+
+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++)
+ score = min(score, levenshtein_distance(command, links[i]));
+
+ // Pass 2: Is ambiguous?
+ int is_ambiguous = 0;
+ for (int i = 0; strcmp(links[i], " ") != 0; i++)
+ is_ambiguous |= score >= levenshtein_distance(command, links[i]) - MAX_DIST;
+
+ // 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];
+
+ // TODO Apply formatting!
+ speak(links[i+2], NULL, NULL); speak(links[i], NULL, NULL); speak(links[i+1], NULL, NULL);
+ }
+
+ return "";
+}
+
+struct termios stored_settings, no_echo;
int read_keyboard = 0;
int speak_finalize(char *ssml) {
while (read_keyboard) {
- char c = getc(stdin);
- if (c == '\033') {
+ if (getc(stdin) == '\033') {
char mark[200];
char fallback[200];
espeak_Cancel();
- c = getc(stdin);
+ char c = getc(stdin);
if (c == 0 || c == -1 || c == '\033' || c == 'q') goto close; // skip [
switch (getc(stdin)) {
case 'A':
@@ 201,6 267,15 @@ int speak_finalize(char *ssml) {
speak(ssml, paragraph_no > 0 ? mark : NULL, NULL);
break;
}
+ } else {
+ // Read in a line
+ tcsetattr(0, TCSANOW, &stored_settings);
+ char *line = NULL;
+ size_t len = 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!
+ tcsetattr(0, TCSANOW, &no_echo);
}
}
espeak_ng_STATUS result = espeak_ng_Synchronize();
@@ 232,7 307,6 @@ FILE *parse_opt_file() {
return ret;
}
-struct termios stored_settings;
int main(int argc, char **argv) {
int speak_err = 0;
hs_init(&argc, &argv);
@@ 270,12 344,12 @@ int main(int argc, char **argv) {
case 'k':
read_keyboard = 1;
// Read input character by character, not line by line.
- struct termios new_settings = stored_settings;
- new_settings.c_lflag &= (~ICANON);
- new_settings.c_lflag &= (~ECHO);
- new_settings.c_cc[VTIME] = 1;
- new_settings.c_cc[VMIN] = 0;
- tcsetattr(0, TCSANOW, &new_settings);
+ 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;
+ tcsetattr(0, TCSANOW, &no_echo);
break;
case 'w':
use_espeak = 1;