~alcinnz/rhapsode

723aea0582ff6905c9eebbe8d724d7b1a1d44cf1 — Adrian Cochrane 3 years ago 226826f
Prepare for navigating links:

Compute levenshtein distances.
Read lines from stdin.
1 files changed, 84 insertions(+), 10 deletions(-)

M src/main.c
M src/main.c => src/main.c +84 -10
@@ 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;