4

My application makes heavy use of text-to-speech (through libespeak). It is written in C++/Qt5 with a QML-based frontend.

I have no formal C++ training (I have a Java background though) and as such I'm not entirely sure how to properly implement some of the more esoteric features.

libespeak supports a callback feature, which is called every time speech is synthesized. The callback function takes three arguments, which I would like to use to visualize the speech. The code below works in the sense that the callback function is called correctly, but not useful since I can't access other member functions or variables.

itemvoice.h

#include "espeak/speak_lib.h"
int callback(short *wav, int numsamples, espeak_EVENT *events);

class ItemVoice : public Item
{
public:
explicit ItemVoice(QQuickItem *parent = 0);
};

itemvoice.cpp

#include "itemvoice.h"
extern int callback(short *wav, int numsamples, espeak_EVENT *events)
{
// do stuff
}
ItemVoice::ItemVoice(QQuickItem *parent):Item(parent)
{
espeak_Initialize(AUDIO_OUTPUT_PLAYBACK,500,NULL,0);
espeak_SetSynthCallback(callback);
}

I would like to make the callback function a member of the ItemVoice class. However if I try (and set the callback function with espeak_SetSynthCallback(ItemVoice::callback), the code won't compile anymore because of arguments which cannot be converted.


UPDATE: The suggestion below works. However, I have now run into another problem. This is what the class looks like now:

itemvoice.h

#include "espeak/speak_lib.h"
int staticCallback(short *wav, int numsamples, espeak_EVENT *events);
class ItemVoice : public Item
{
    Q_OBJECT

public:
    explicit ItemVoice(QQuickItem *parent = 0);
    void startSpeaking();
    void stopSpeaking();

signals:
    void updateGUI();
}

itemvoice.cpp

#include "itemvoice.h"
ItemVoice::ItemVoice(QQuickItem *parent):Item(parent)
{
    espeak_Initialize(AUDIO_OUTPUT_PLAYBACK,500,NULL,0);
    espeak_SetSynthCallback(staticCallback);
}

int staticCallback(short *wav, int numsamples, espeak_EVENT *events)
{
espeak_EVENT_TYPE type=events->type;
if(type==2) // start sentence
    (static_cast<ItemVoice*>(events[0].user_data))->startSpeaking();
else if(type==6) // stop sentence
    (static_cast<ItemVoice*>(events[0].user_data))->stopSpeaking(); 
}

void ItemVoice::startSpeaking()
{
    //do stuff
    updateGUI();    
}

void ItemVoice::stopSpeaking()
{
    // do stuff
    updateGUI();
}

This works correctly. startSpeaking() is called when synthesis begins, and stopSpeaking() when it stops. The problem is that I need to send a Qt signal to update the GUI (updateGUI), and about a second after it's sent, my application crashes with a segmentation fault, even if the signal is not connected anywhere. It works perfectly otherwise.

Any idea?

Thanks for reading!

Ralf Van Bogaert
  • 103
  • 1
  • 10
  • Do you want to have callback in multiple ItemVoices instances? – Max Go Oct 02 '14 at 08:56
  • * I meant to have callback function called in multiple instances of ItemVoices on each speech recognition event – Max Go Oct 02 '14 at 09:06
  • No, I need only a single instance – Ralf Van Bogaert Oct 02 '14 at 11:55
  • If you really need only single instance of ItemVoices, then you can shape it to Singleton http://stackoverflow.com/a/1008289/2266412 and just call getInstance() static method to access your instance from your callback function... if you really need to call espeak_Synth like in answer below, then passing `this` pointer looks like good approach too. – Max Go Oct 02 '14 at 12:01
  • Thanks for the suggestion, but I'm not sure using a singleton will work here since the object is instantiated from QML, not C++. It subclasses QQuickItem which is registered in the QQmlContext – Ralf Van Bogaert Oct 02 '14 at 12:35
  • What's the nature of your seg fault? Do you get a stack trace? Is it possible that this multi-threaded and your callback is being invoked on a different thread to the thread on which it was queued? – Component 10 Oct 02 '14 at 13:14
  • No stack trace. The code above is actually in a separate plugin (.so file on Linux) which is loaded in the main application at runtime (using QPluginLoader). Then again I've created many of them and I've never encountered this problem anywhere else. Not using threads in this plugin. – Ralf Van Bogaert Oct 03 '14 at 09:35

1 Answers1

2

There is no direct way to do what you want. In your case you are lucky because there is void* user_data field in espeak_EVENT. You can set it to this when you call espeak_Synth():

void ItemVoice::synthSpeech() {
    espeak_Synth(...., this);
}

So in the callback (which is still a global function, or a static function in ItemVoice) you can do roughly this:

int staticCallback(short *wav, int numsamples, espeak_EVENT *events) {
    if (numsamples > 0)
        return (static_cast<ItemVoice*>(events[0].user_data))->nonStaticCallback(wav, numsamples, events);
}
Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • Cool, that appears to work. Thank you. I've run into another problem though. I have updated the original post. – Ralf Van Bogaert Oct 02 '14 at 11:58
  • @RalfVanBogaert you don't check `numsamples` in your code, can it really be zero? Anyway you should debug your application to catch a moment of crash. – Anton Savin Oct 02 '14 at 13:06
  • numsamples is 0 every time, I don't use it anyway, I removed it otherwise the functions would never be called. Can't see how it is related to the crash. I'm trying to get debugging going using GDB, will take me a while until I figured out how to properly use it – Ralf Van Bogaert Oct 02 '14 at 14:07
  • @RalfVanBogaert You can debug from within Qt Creator. That'd be much simpler, in most cases you need to know nothing about gdb. – Kuba hasn't forgotten Monica Oct 02 '14 at 21:27
  • That's what I'm trying, but honestly, I don't even know what I'm looking for. Updated the original post. – Ralf Van Bogaert Oct 03 '14 at 09:31