~alcinnz/rhapsode

edd84882f3a490907671df549c7ff54e276bab26 — Adrian Cochrane 4 years ago c6d7cd5
Output audio cues in live speech.

Turned out I was overcomplicating things, all I needed to do was use file paths not URIs.
eSpeak NG handled it from there.
5 files changed, 12 insertions(+), 28 deletions(-)

D link.oga
A link.wav
M src/Render.hs
M src/main.c
M useragent.css
D link.oga => link.oga +0 -0
A link.wav => link.wav +0 -0
M src/Render.hs => src/Render.hs +7 -7
@@ 120,15 120,15 @@ instance CSS.StyleSheet StyleAssets where
                    Just uri <- [parseAbsoluteURI $ Txt.unpack text]]
            )

downloadAssets session mimes (StyleAssets _ assets) =
downloadAssets session mimes (StyleAssets _ assets) = do
    -- FIXME delete these temp files.
    forConcurrently assets (\uri ->
        fetchURL' session mimes uri >>= saveAsset mimes) >>=
        return . zip assets
    localUris <- forConcurrently assets (\uri ->
        fetchURL' session mimes uri >>= saveAsset mimes)
    return $ zip assets [uri {uriScheme = "", uriAuthority = Nothing} | uri <- localUris]

-- variant of HURL fetchURL which includes about:link.oga & about:bullet-point.wav
fetchURL' _ _ (URI "about:" Nothing "link.oga" _ _) =
    return ("application/ogg", Right $ B.fromStrict $(embedFile "link.oga"))
-- variant of HURL fetchURL which includes about:link.wav & about:bullet-point.wav
fetchURL' _ _ (URI "about:" Nothing "link.wav" _ _) =
    return ("application/ogg", Right $ B.fromStrict $(embedFile "link.wav"))
fetchURL' _ _ (URI "about:" Nothing "bulletpoint.wav" _ _) =
    return ("audio/vnd.wav", Right $ B.fromStrict $(embedFile "bulletpoint.wav"))
fetchURL' s m u = fetchURL s m u

M src/main.c => src/main.c +3 -19
@@ 58,27 58,11 @@ int read_mono(SNDFILE *fd, SF_INFO *info, short *out) {
    return i;
}

int synth_callback(short *wav, int numsamples, espeak_EVENT *events) {
int save_audio(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;
        if (events->type == espeakEVENT_PLAY) {
            SF_INFO info;
            info.format = 0;
            short buf[BUFFER_LEN];
            int len;
            SNDFILE *fd = sf_open(events->id.name + strlen("file://"), SFM_READ, &info);
            if (fd == NULL || info.channels > MAX_CHANNELS) {
                fprintf(stderr, "Failed to open %s: %s\n", events->id.name + strlen("file://"),
                    sf_strerror(fd));
                events++;
                continue;
            }
            while ((len = read_mono(fd, &info, buf)) != 0)
                sf_write_short(fd_wav, buf, len);
            sf_close(fd);
        }
        events++;
    }



@@ 105,8 89,7 @@ int speak_initialize() {

    if (path_wav != NULL) {
        result = espeak_ng_InitializeOutput(ENOUTPUT_MODE_SYNCHRONOUS, 0, NULL);
        espeak_SetUriCallback(should_play);
        espeak_SetSynthCallback(synth_callback);
        espeak_SetSynthCallback(save_audio);
    } else {
        result = espeak_ng_InitializeOutput(ENOUTPUT_MODE_SPEAK_AUDIO, 0, NULL);
    }


@@ 115,6 98,7 @@ int speak_initialize() {
        return 3;
    }

    //espeak_SetUriCallback(should_play);
    samplerate = espeak_ng_GetSampleRate();
    return 0;
}

M useragent.css => useragent.css +2 -2
@@ 31,8 31,8 @@ hr {pause: x-strong}
p, pre, samp, blockquote {pause: strong}
pre, address, samp {speak-as: literal-punctuation}
pre, samp, code {voice: neutral 2}
[href], :link {cue-after: url(about:link.oga) !important; voice-pitch: low}
:link:visited {cue-after: url(about:link.oga) -1db !important}
[href], :link {cue-after: url(about:link.wav) !important; voice-pitch: low}
:link:visited {cue-after: url(about:link.wav) -1db !important}
img {voice-volume: soft; content: attr(src)}
img[alt] {content: attr(alt)}