@@ 5,7 5,9 @@
#include <fcntl.h>
#include "HsFFI.h"
+#include <espeak-ng/espeak_ng.h>
+/* Exported Haskell functions/types */
struct session;
struct session *c_newSession();
void c_freeSession(struct session*);
@@ 20,13 22,116 @@ char *c_renderDoc(struct session*, struct page*);
char **c_extractLinks(struct page*);
char **c_docLinksAndRendering(struct session*, struct page*); // FIXME segfaults.
+/* espeak-ng integration. Based on the espeak-ng command source code. */
+FILE *fd_wav = NULL;
+static int samplerate;
+espeak_ng_ERROR_CONTEXT context;
+
+void write4b(int value) {
+ // Write 4 bytes to a file, least significant first
+ int ix;
+
+ for (ix = 0; ix < 4; ix++) {
+ fputc(value & 0xff, fd_wav);
+ value = value >> 8;
+ }
+}
+
+int wrote_header = 0;
+void write_header() {
+ static unsigned char wave_hdr[44] = {
+ 'R', 'I', 'F', 'F', 0x24, 0xf0, 0xff, 0x7f, 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ',
+ 0x10, 0, 0, 0, 1, 0, 1, 0, 9, 0x3d, 0, 0, 0x12, 0x7a, 0, 0,
+ 2, 0, 0x10, 0, 'd', 'a', 't', 'a', 0x00, 0xf0, 0xff, 0x7f
+ };
+
+ fwrite(wave_hdr, 1, 24, fd_wav);
+ write4b(samplerate);
+ write4b(samplerate * 2);
+ fwrite(&wave_hdr[32], 1, 12, fd_wav);
+}
+void close_wav_file() {
+ fflush(fd_wav);
+ unsigned int pos = ftell(fd_wav);
+
+ if (fseek(fd_wav, 4, SEEK_SET) != -1) write4b(pos - 8);
+ if (fseek(fd_wav, 40, SEEK_SET) != -1) write4b(pos - 44);
+
+ fclose(fd_wav);
+}
+
+int synth_callback(short *wav, int numsamples, espeak_EVENT *events) {
+ if (wav == NULL) return 0;
+
+ while (events->type != 0) {
+ if (events->type == espeakEVENT_SAMPLERATE) samplerate = events->id.number;
+ events++;
+ }
+
+ if (!wrote_header) write_header();
+ if (numsamples > 0) fwrite(wav, numsamples*2, 1, fd_wav);
+ return 0;
+}
+
+int speak_initialize() {
+ espeak_ng_InitializePath(NULL);
+ context = NULL;
+ espeak_ng_STATUS result = espeak_ng_Initialize(&context);
+ if (result != ENS_OK) {
+ espeak_ng_PrintStatusCodeMessage(result, stderr, context);
+ espeak_ng_ClearErrorContext(&context);
+ return 2;
+ }
+
+ if (fd_wav != stdout) {
+ result = espeak_ng_InitializeOutput(ENOUTPUT_MODE_SYNCHRONOUS, 0, NULL);
+ espeak_SetSynthCallback(synth_callback);
+ } else {
+ result = espeak_ng_InitializeOutput(ENOUTPUT_MODE_SPEAK_AUDIO, 0, NULL);
+ }
+ if (result != ENS_OK) {
+ espeak_ng_PrintStatusCodeMessage(result, stderr, context);
+ return 3;
+ }
+
+ samplerate = espeak_ng_GetSampleRate();
+ return 0;
+}
+void speak(char *ssml) {
+ int flags = espeakCHARS_AUTO | espeakPHONEMES | espeakENDPAUSE | espeakCHARS_UTF8 | espeakSSML;
+ espeak_Synth(ssml, strlen(ssml)+1, 0, POS_CHARACTER, 0, flags, NULL, NULL);
+}
+int speak_finalize() {
+ espeak_ng_STATUS result = espeak_ng_Synchronize();
+ if (result != ENS_OK) {
+ espeak_ng_PrintStatusCodeMessage(result, stderr, context);
+ return 4;
+ }
+
+ if (fd_wav != stdout) close_wav_file();
+ espeak_ng_Terminate();
+ return 0;
+}
+
+/* Main driver */
void write_links(FILE *dest, char **links) {
for (int i = 0; strcmp(links[i], " ") != 0; i++) {
fprintf(dest, "%s%c", links[i], (i % 3) == 2 ? '\n' : '\t');
}
}
+FILE *parse_opt_file(char *mode) {
+ FILE *ret = optarg != NULL ? fopen(optarg, mode) : stdout;
+ if (ret == NULL) {
+ fprintf(stderr, "Failed to open file %s\n", optarg);
+ hs_exit();
+ exit(-1);
+ }
+ return ret;
+}
+
int main(int argc, char **argv) {
+ int speak_err = 0;
hs_init(&argc, &argv);
char *mimes = "text/html text/xml application/xml application/xhtml+xml text/plain";
@@ 35,26 140,19 @@ int main(int argc, char **argv) {
int c;
opterr = 0;
- while ((c = getopt(argc, argv, "xs::l::h")) != -1) {
+ while ((c = getopt(argc, argv, "xs::l::w::h")) != -1) {
switch (c) {
case 'x':
mimes = "text/xml application/xml application/xhtml+xml text/html text/plain";
break;
case 's':
- fd_ssml = optarg != NULL ? fopen(optarg, "w") : stdout;
- if (fd_ssml == NULL) {
- fprintf(stderr, "Failed to open file %s\n", optarg);
- hs_exit();
- return -1;
- }
+ fd_ssml = parse_opt_file("w");
break;
case 'l':
- fd_links = optarg != NULL ? fopen(optarg, "w") : stdout;
- if (fd_links == NULL) {
- fprintf(stderr, "Failed to open file %s\n", optarg);
- hs_exit();
- return -1;
- }
+ fd_links = parse_opt_file("w");
+ break;
+ case 'w':
+ fd_wav = parse_opt_file("wb");
break;
case '?':
fprintf(stderr, "Invalid flag %c\n\n", optopt);
@@ 68,28 166,35 @@ int main(int argc, char **argv) {
fprintf(stderr, "\t\t\tIf both -s & -l are enabled without an argument, writes to stderr instead.\n");
hs_exit();
- return c == 'h' ? 0 : -1;
+ return c == 'h' ? 0 : 1;
}
}
if (fd_ssml == stdout && fd_links == stdout) fd_links = stderr;
- if (fd_ssml == NULL && fd_links == NULL) fd_ssml = stdout;
+ if (fd_ssml == NULL && fd_links == NULL && fd_wav == NULL) fd_wav = stdout;
struct session *session = c_newSession();
struct page *referer = c_initialReferer();
+ if (fd_wav != NULL) speak_err = speak_initialize();
for (int i = optind; i < argc; i++) {
- printf("%s\n", argv[i]);
+ if (fd_wav != NULL && speak_err == 0) speak(argv[i]);
+ else printf("%s\n", argv[i]);
+
struct page *page = c_fetchURL(session, mimes, referer, argv[i]);
char *ssml = c_renderDoc(session, page);
char **links = c_extractLinks(page);
if (fd_ssml != NULL) fprintf(fd_ssml, "%s\n", ssml);
if (fd_links != NULL) write_links(fd_links, links);
+ if (fd_wav != NULL& speak_err == 0) speak(ssml);
c_freePage(page);
}
+ if (fd_wav != NULL & speak_err == 0) speak_err = speak_finalize();
c_freePage(referer);
c_freeSession(session);
hs_exit();
+
+ return speak_err;
}