eSpeak

@yaxu you mentioned on the last tidal meetup that you had been working on an epeak project. i couldn't quite figure out how to implement your code. it wouldn't evaluate in a .tidal file and it didn't like being added to the boottidal.hs file. it seems to not recognize the oHandshake bit. any advice?

You can find info about using custom OSC targets here OSC | Tidal Cycles

If oHandshake is causing errors that means you have an older version of tidalcycles. You could upgrade tidal or just remove that line (and the preceding comma).

1 Like

aha, super! let me dig into this OSC business; thank you for sending. i am probably due for a version update as well! thanks Alex!

hey @yaxu! ok, so @cleary and i have been working through the various modules (liblo, espeak-ng) and endpoints (python script) required to run the custom eSpeak OSC target. back in the editor, however, we're getting hung up on 1) the so-called "stream" and whether or not we need to essentially piggyback on the stream that's already running when tidal boots (or somehow specify another one) and 2) what the final code should look like. here's our journey so far.

--this works:

let espeakTarget :: Target
    espeakTarget = Target {oName = "espeak",
                           oAddress = "127.0.0.1",
                           oPort = 6050,
                           oBusPort = Nothing,
                           oLatency = 0.02,
                           oWindow = Nothing,
                           oSchedule = Live,
                           oHandshake = False
                          }
    espeakShape :: OSC
    espeakShape = OSC "/play" $ ArgList [("word", required),
                                         ("language", sDefault "german"),
                                         ("pitch", fDefault 32),
                                         ("speed", fDefault 150)
                                        ]
    espeak = [(espeakTarget, [espeakShape])]

let x1 = streamReplace stream 1

--this throws an error

stream <- startStream defaultConfig espeak

That port isn't available, perhaps another Tidal instance is already listening on that port?

--and this is the presumed code (only one required arg, "word"), does not work

x1 $ espeak "test"

• Couldn't match expected type ‘[Char] -> ControlPattern’ with actual type ‘[(Target, [OSC])]’ • The function ‘espeak’ is applied to one argument, but its type ‘[(Target, [OSC])]’ has none In the second argument of ‘($)’, namely ‘espeak "test"’ In the expression: x1 $ espeak "test"

You can mix them in the same stream as superdirt like this:

:{
espeakTarget :: Target
espeakTarget = Target {oName = "espeak",
                       oAddress = "127.0.0.1",
                       oPort = 6050,
                       oBusPort = Nothing,
                       oLatency = 0.02,
                       oWindow = Nothing,
                       oSchedule = Live,
                       oHandshake = False
                      }
               
espeakShape :: OSC
espeakShape = OSC "/espeak" $ ArgList [("word", required),
                                       ("language", sDefault "german"),
                                       ("pitch", fDefault 32),
                                       ("speed", fDefault 150)
                                      ]
:}

:{
let word = pS "word"
    pitch = pF "pitch"
:}

:{
  tidal <- startStream defaultConfig [(espeakTarget, [espeakShape])
                                     , (superdirtTarget {oLatency = 0.1}, [superdirtShape])
                                   ]
:}

Be sure to remove/comment out the current call to startStream or startTidal.

Then you can do

d1 $ word "hello world"

The pitch and speed parameters should work too. I didn't manage to get the language parameter to work though.

1 Like

wicked. i'm still getting no sound but at least no errors on the tidal side of things. i'm guessing it's related to the python endpoint. i did a pip install py-espeak-ng but am not sure that's the correct module that your python script needs and i might also be having some audio routing issues because i noticed also you might be sending it through jack and i am using blackhole. one step closer! thanks alex!

qq @yaxu are you using some sort of other audio output module in python like pyAudio, pyAudioAnalyzer, or sounddevice to send to your jack router setup?

No I think it was just outputting to the default device (pulseaudio)

1 Like

I'd really love to get this to work, but these sparse pieces of information are a bit confusing to me. I'm summing up in this post what I think I'm getting, please correct or complete me if you can!

From what I've gathered:

:{
espeakTarget :: Target
espeakTarget = Target {oName = "espeak",
                       oAddress = "127.0.0.1",
                       oPort = 6050,
                       oBusPort = Nothing,
                       oLatency = 0.02,
                       oWindow = Nothing,
                       oSchedule = Live,
                       oHandshake = False
                      }
               
espeakShape :: OSC
espeakShape = OSC "/espeak" $ ArgList [("word", required),
                                       ("language", sDefault "german"),
                                       ("pitch", fDefault 32),
                                       ("speed", fDefault 150)
                                      ]
:}

:{
let word = pS "word"
    pitch = pF "pitch"
:}

:{
  tidal <- startStream defaultConfig [(espeakTarget, [espeakShape])
                                     , (superdirtTarget {oLatency = 0.1}, [superdirtShape])
                                   ]
:}

This should go into BootTidal.hs, and the last part should replace the already present call to startStream. Can BootTidal.hs be left this way after adding this or will it interfere with Tidal's normal behavior?

To create a "espeak-able" channel, one has to execute let x1 = streamReplace stream 1, and then to play something on this channel, execute something along the lines of x1 $ espeak "test" # language "french" # pitch "40" # speed 200

You then have to run this script: https://github.com/yaxu/aacp/blob/main/espeak/espeakosc.py , having beforehand installed all the necessary dependencies (for me it was apt install python3-liblo python3-espeak and pip3 install py-espeak-ng). This script will listen to the /espeak OSC messages sent by Tidal, and generate speech accordingly.

As of the time I'm writing this, I still haven't been able to generate any speech, but this seems to be more on the side of python than Tidal, so this is what I'll investigate. I'll let you know if I need to submit any further corrections.

1 Like

Hi @th4, just going back through your instructions on a fairly fresh tidal install..

Can BootTidal.hs be left this way after adding this or will it interfere with Tidal's normal behavior?

I think it will work as normal like this.

the last part should replace the already present call to startStream

In the default BootTidal.hs the call is to the older startTidal call, but yes. You can make sure you're editing the right BootTidal.hs file by putting something like putStrLn "hello" in the file and see if that pops up if you restart tidal.

You don't need to create a separate x1 function, you can use d1, d2 etc. It will only send OSC messages to the espeak thingie if the word parameter is present in the pattern. Likewise, it will only send OSC to SuperDirt if the s (or sound) parameter is present.

This works for me:

d1 $ word "hello"

Does espeak-ng hello from a terminal window make sound for you? If not maybe you have jack running which is blocking pulseaudio desktop audio. You might fix that by installing pulseaudio-module-jack, or switching to the new pipewire replacement for jack which solves these problems nicely.

espeak-ng hello indeed didn't work at first, turns out I also needed to install the espeak-ng package and not just the python library.

I also needed to install pulseaudio-module-jack and configure it by adding

load-module module-jack-sink
load-module module-jack-source

to /etc/pulse/default.pa (which I hadn't done yet on my new computer but knew I would eventually need to do).

Works like a charm now, thanks!

1 Like