PDA

View Full Version : emit a signal from inside a callback routine



franco.amato
8th December 2009, 01:40
Hi to all,
I' writing an audio editor using fmod and Qt.
Hi have a fmod callback that's called by the fmod engine when a sound is stopped to play and from it I would emit a signal.
Here the callback that's part of a class:


class AudioDevice : public QObject
{
Q_OBJECT

public:
static FMOD_RESULT F_CALLBACK endCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
unsigned int commanddata1, unsigned int commanddata2)
{
(void)commanddata1; // Unused (to avoid warnings)
(void)commanddata2; // Unused (to avoid warnings)

SoundData* currentSound;

switch(type)
{
case FMOD_CHANNEL_CALLBACKTYPE_END:
{
FMOD_RESULT result;
FMOD::Channel *currentChannel = (FMOD::Channel *)channel;

void *ud = NULL;
result = currentChannel->getUserData( &ud );

currentSound = (SoundData*)ud;

/* inform that sound stopped */
emit( soundStopped() );// <------- THIS ONE
break;
}
default:
break;
}

return FMOD_OK;
}
public slots:
// Initialize the audio system
bool init();

signals:
void soundStopped();
..more code...



The compiler don't like the emit call.
It says

error C2352: 'AudioDevice::soundStopped' : illegal call of non-static member function

How can I do?

Best

JohannesMunk
8th December 2009, 02:52
Hi!

The compiler is correct. You are calling a non static member from inside a static function. That's a no go. What would be the this object of that call?

Your callback probably cannot be non-static, so you will have to reference your audiodevice in another way.

Usually you would use the "user-data" of the callback for that. When you set the callback up, you can pass a pointer to your Audiodevice as commanddata1.



class AudioDevice : public QObject
{ Q_OBJECT
friend FMOD_RESULT F_CALLBACK endCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
unsigned int commanddata1, unsigned int commanddata2);
signals:
void soundStopped();
protected:
void DoEndCallBack() {emit soundStopped();}
}

..

FMOD_RESULT F_CALLBACK endCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
unsigned int commanddata1, unsigned int commanddata2)
{
AudioDevice* ad = dynamic_cast<AudioDevice*>(commanddata1);
if (ad)
ad->DoEndCallBack();
..
}


Using the friends approach you can even declare the DoEndCallBack protected..

HIH

Johannes

franco.amato
8th December 2009, 05:03
Hi!

The compiler is correct. You are calling a non static member from inside a static function. That's a no go. What would be the this object of that call?

Your callback probably cannot be non-static, so you will have to reference your audiodevice in another way.

Usually you would use the "user-data" of the callback for that. When you set the callback up, you can pass a pointer to your Audiodevice as commanddata1.



class AudioDevice : public QObject
{ Q_OBJECT
friend FMOD_RESULT F_CALLBACK endCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
unsigned int commanddata1, unsigned int commanddata2);
signals:
void soundStopped();
protected:
void DoEndCallBack() {emit soundStopped();}
}

..

FMOD_RESULT F_CALLBACK endCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
unsigned int commanddata1, unsigned int commanddata2)
{
AudioDevice* ad = dynamic_cast<AudioDevice*>(commanddata1);
if (ad)
ad->DoEndCallBack();
..
}


Using the friends approach you can even declare the DoEndCallBack protected..

HIH

Johannes

Hi thank you very much.
I didn't understand. What does the DoEndCallback do?
Sorry but in this case commanddata1 and commanddata2 are always 0.
This is the part where I use the userdata:

case FMOD_CHANNEL_CALLBACKTYPE_END:
{
FMOD_RESULT result;
FMOD::Channel *currentChannel = (FMOD::Channel *)channel;

void *ud = NULL;
result = currentChannel->getUserData( &ud ); //<---USERDATA

currentSound = (SoundData*)ud;

/* inform that sound stopped */
currentSound->soundFinished(); //<-- I call a routine instead of emiting signals
break;
}
but I don't cast it to an AudioDevice type. AudioDevice is a singleton.
I cast to a SoundData type ( in my example )..

Is a bit difficult. May be I can not use signals in this case

Tanuki-no Torigava
8th December 2009, 05:40
Hi. When I met the same problem I solved that with startTimer/killTimer and void timerEvent ( QTimerEvent * event ) reimplementation. Then what you can do is to set any Boolean variable (or QMutex based variable if you have multiple access) to true (lock) accordingly and process that inside timerEvent.

Typical solution might be:




static const int timeScale = 50; // 50 Msec

class A: public QObject
{
Q_OBJECT

public:

Q_INVOKABLE A( QObject * parent = 0 )
: QObject(parent),
m_check(false),
m_timer(0)
{
m_timer = startTimer(timeScale);
}
~A()
{
if (m_timer > 0)
killTimer(m_timer);
}

public slots:

void handler()
{
if (m_check)
return;
// do something here
m_check = true;
}

protected:
void timerEvent ( QTimerEvent * event)
{
if (m_check)
{
// do something
emit some_signal(some_args);
m_check != m_check; // Just quicker
}
}
private:

bool m_check;
int m_timer;
}


Good luck.

franco.amato
8th December 2009, 06:00
Hi. When I met the same problem I solved that with startTimer/killTimer and void timerEvent ( QTimerEvent * event ) reimplementation. Then what you can do is to set any Boolean variable (or QMutex based variable if you have multiple access) to true (lock) accordingly and process that inside timerEvent.

Typical solution might be:




static const int timeScale = 50; // 50 Msec

class A: public QObject
{
Q_OBJECT

public:

Q_INVOKABLE A( QObject * parent = 0 )
: QObject(parent),
m_check(false),
m_timer(0)
{
m_timer = startTimer(timeScale);
}
~A()
{
if (m_timer > 0)
killTimer(m_timer);
}

public slots:

void handler()
{
if (m_check)
return;
// do something here
m_check = true;
}

protected:
void timerEvent ( QTimerEvent * event)
{
if (m_check)
{
// do something
emit some_signal(some_args);
m_check != m_check; // Just quicker
}
}
private:

bool m_check;
int m_timer;
}


Good luck.

I don't know how to adapt your code for my purpose, but how can be possible to connect
a slot of an object that's created when the gui is started with a signal of an object that is created later?
I get an error

Tanuki-no Torigava
8th December 2009, 06:32
I don't know how to adapt your code for my purpose, but how can be possible to connect
a slot of an object that's created when the gui is started with a signal of an object that is created later?
I get an error


That is easy. Let's say you create object B from some method of the object A, so it will look like that:



A::someMethod()
{
B* b = new B(this);
connect(b, SIGNAL(b_signal(mask)), this, SLOT(signal_handler);

}


So, you get the idea. ;)

franco.amato
8th December 2009, 07:17
That is easy. Let's say you create object B from some method of the object A, so it will look like that:



A::someMethod()
{
B* b = new B(this);
connect(b, SIGNAL(b_signal(mask)), this, SLOT(signal_handler);

}


So, you get the idea. ;)

Hi yes,
but what the key "mask" means?
Seems that I have to connect after the new

JohannesMunk
8th December 2009, 12:06
Hi there!

You seem to miss the point of the commanddata.. That is data you pass along / define once when you call the callback-setup! You seem to pass 0 at the moment, that is why its zero. Just pass a pointer to your AudioDevice and it should work. That pointer-data then is passed to the callback each time it is called. At least that is, how callbacks usually work..

You should have mentioned, that AudioDevice is a singleton.. Because than you can call the DoEndCallBack directly, even if your callback doesn't provide a user-defined-data (which I think it does => commanddata).

protected:
void DoEndCallBack() {emit soundStopped();}

DoEndCallback just emits the soundStopped signal, as I defined it... But you can of course code it directly, see below:

Let me give you a more complete Callback scenario:



class TDAQCaptureThread : public QThread
{
friend int32 CVICALLBACK DoneCallback(TaskHandle taskHandle, int32 status, void *callbackData);
Q_OBJECT
signals:
void Finished();
protected:
void run();
};

#include "DAQCaptureThread.h"

int32 CVICALLBACK DoneCallback(TaskHandle taskHandle, int32 status, void *callbackData);

void TDAQCaptureThread::run()
{
...

DAQmxRegisterDoneEvent(taskHandle,1,DoneCallback,t his);

..

DAQmxStartTask(taskHandle);

exec();

...

}

int32 CVICALLBACK DoneCallback(TaskHandle taskHandle, int32 status, void *callbackData)
{
..

emit ((TDAQCaptureThread*)callbackData)->Finished();

...

return 0;
}


The DAQmxRegisterDoneEvent is defined int the used DAQmx library as follows:


int32 __CFUNC DAQmxRegisterDoneEvent(TaskHandle task, uInt32 options, DAQmxDoneEventCallbackPtr callbackFunction, void *callbackData);


This callbackData is what I'm talking about.. If you decide one day to have several AudioDevices :->

HIH

Johannes

franco.amato
8th December 2009, 18:03
Hi there!

You seem to miss the point of the commanddata.. That is data you pass along / define once when you call the callback-setup! You seem to pass 0 at the moment, that is why its zero. Just pass a pointer to your AudioDevice and it should work. That pointer-data then is passed to the callback each time it is called. At least that is, how callbacks usually work..

You should have mentioned, that AudioDevice is a singleton.. Because than you can call the DoEndCallBack directly, even if your callback doesn't provide a user-defined-data (which I think it does => commanddata).

protected:
void DoEndCallBack() {emit soundStopped();}

DoEndCallback just emits the soundStopped signal, as I defined it... But you can of course code it directly, see below:

Let me give you a more complete Callback scenario:



class TDAQCaptureThread : public QThread
{
friend int32 CVICALLBACK DoneCallback(TaskHandle taskHandle, int32 status, void *callbackData);
Q_OBJECT
signals:
void Finished();
protected:
void run();
};

#include "DAQCaptureThread.h"

int32 CVICALLBACK DoneCallback(TaskHandle taskHandle, int32 status, void *callbackData);

void TDAQCaptureThread::run()
{
...

DAQmxRegisterDoneEvent(taskHandle,1,DoneCallback,t his);

..

DAQmxStartTask(taskHandle);

exec();

...

}

int32 CVICALLBACK DoneCallback(TaskHandle taskHandle, int32 status, void *callbackData)
{
..

emit ((TDAQCaptureThread*)callbackData)->Finished();

...

return 0;
}


The DAQmxRegisterDoneEvent is defined int the used DAQmx library as follows:


int32 __CFUNC DAQmxRegisterDoneEvent(TaskHandle task, uInt32 options, DAQmxDoneEventCallbackPtr callbackFunction, void *callbackData);


This callbackData is what I'm talking about.. If you decide one day to have several AudioDevices :->

HIH

Johannes

In this case commanddata1 and commanddata2 are not the user data ( fmod callbacks ). In this case they are 0. User data are sent by another routine setUserdata and I got it from inside the callback by getUserdData() :D.

Thank you

JohannesMunk
8th December 2009, 18:15
All right, I give up :->

So your solution is to just emit the signal through the singleton?



class AudioDevice : public QObject
{ Q_OBJECT
friend FMOD_RESULT F_CALLBACK endCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
unsigned int commanddata1, unsigned int commanddata2);

public:
static AudioDevice* singleton()
{
if (!instance)
instance = new AudioDevice();
return instance;
}
signals:
void soundStopped();

protected:
AudioDevice() {}

private:
static AudioDevice *instance;
};

AudioDevice* AudioDevice::instance = 0;

FMOD_RESULT F_CALLBACK endCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
unsigned int commanddata1, unsigned int commanddata2)
{
..
emit AudioDevice::singleton()->soundStopped();
..
}HIH

Johannes

franco.amato
8th December 2009, 22:53
All right, I give up :->

So your solution is to just emit the signal through the singleton?



class AudioDevice : public QObject
{ Q_OBJECT
friend FMOD_RESULT F_CALLBACK endCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
unsigned int commanddata1, unsigned int commanddata2);

public:
static AudioDevice* singleton()
{
if (!instance)
instance = new AudioDevice();
return instance;
}
signals:
void soundStopped();

protected:
AudioDevice() {}

private:
static AudioDevice *instance;
};

AudioDevice* AudioDevice::instance = 0;

FMOD_RESULT F_CALLBACK endCallback(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
unsigned int commanddata1, unsigned int commanddata2)
{
..
emit AudioDevice::singleton()->soundStopped();
..
}HIH

Johannes

Hi I didn't well understand the difference between your last code and the code you suggested to me before.
Can you explain to me?
Best Regards

JohannesMunk
8th December 2009, 23:05
Sure!

The only difference is how we get a pointer to our audiodevice from inside the callback.

The first approach used the callbacks commanddata to get a reference to the audiodevice. That would allow for several audiodevices to exist simultaneously in your application.

Then we found out, that you can't set the commanddata for that purpose.

Then you said, that you have only one (singleton) audiodevice. That means that we can easily access this single (global) audiodevice.. to emit the signal from everywhere we want. In the way I have shown to you (friends + singleton access)

And once the signal gets emitted correctly the common signal slot mechanism - which I didn't show how to setup - will assure that your currentsound will "get the message"!

Let me know if I was talking sense now :->

Johannes

franco.amato
9th December 2009, 01:46
Sure!

The only difference is how we get a pointer to our audiodevice from inside the callback.

The first approach used the callbacks commanddata to get a reference to the audiodevice. That would allow for several audiodevices to exist simultaneously in your application.

Then we found out, that you can't set the commanddata for that purpose.

Then you said, that you have only one (singleton) audiodevice. That means that we can easily access this single (global) audiodevice.. to emit the signal from everywhere we want. In the way I have shown to you (friends + singleton access)

And once the signal gets emitted correctly the common signal slot mechanism - which I didn't show how to setup - will assure that your currentsound will "get the message"!

Let me know if I was talking sense now :->

Johannes

Yes sure. Thank you very much your explanation was clear.
A question. I would connect with the signal/slot mechanism 2 objects ( audiodevice + another one ). The problem is that eather one is part of the other.
AudioDevice has not an instance of the OtherObject and the OtherObject has not an instance of the AudioDevice.
The 2 objects are indipendent.

I hpe you can help me.

Best Regards,
Franco

JohannesMunk
9th December 2009, 02:00
That's perfectly fine for signals and slots:



AudioDevice* ad = new AudioDevice();
...
SoundData* sd = new SoundData();
...
QObject::connect(ad,SIGNAL(soundStopped()),sd,SLOT (someSLOT()));
or using the singleton approach to get the AudioDevice:



SoundData* sd = new SoundData();
...
QObject::connect(AudioDevice::singleton(),SIGNAL(s oundStopped()),sd,SLOT(someSLOT()));
For signals and slots to work both classes have to inherit someway along the line from QObject and include the Q_OBJECT macro..

They don't have to know anything from each other. Thats the beauty of signals!

HIH

Johannes

franco.amato
9th December 2009, 16:31
That's perfectly fine for signals and slots:



AudioDevice* ad = new AudioDevice();
...
SoundData* sd = new SoundData();
...
QObject::connect(ad,SIGNAL(soundStopped()),sd,SLOT (someSLOT()));
or using the singleton approach to get the AudioDevice:



SoundData* sd = new SoundData();
...
QObject::connect(AudioDevice::singleton(),SIGNAL(s oundStopped()),sd,SLOT(someSLOT()));
For signals and slots to work both classes have to inherit someway along the line from QObject and include the Q_OBJECT macro..

They don't have to know anything from each other. Thats the beauty of signals!

HIH

Johannes

It's fantastic. I love Qt.
In which part of the code I have to write the QObject::connect?
In that case ( a signal emitted by a callback ) I have to specify the Queued connections flag right?

Best

JohannesMunk
9th December 2009, 16:50
In which part of the code I have to write the QObject::connect?

Any. I suggest where you create the sounddata. If your sounddata is more generic than just data being played back, I suggest to set it up when you prepare it for playback. But then you will have to remove the connection after playback ended, to avoid multiple connections, when you play it back again. I don't know enough about the layout of your program to help you here..

BTW: As the connect function is a static function of the QObject class, you only need to write "QObject::" in front of connect, if you are outside a QObject class.. Inside you can just write "connect(..)". But I showed you QObject::connect .. to underline that you can call this from everywhere.



In that case ( a signal emitted by a callback ) I have to specify the Queued connections flag right?

That depends on your callback library. Some execute the callback within the thread context of your application (synchronized) and some don't (async). Perhaps you can specify that when you set the callback. When the callback is already synchronized it doesn't make a lot of sense to go through qt's message system again (=>direct connection). Unless the callback needs to return very quickly and you want to do something big in your connected slot. If the callback is called asynchronously (in the threadcontext of the libraray) and you want to access GUI elements or something else that is not thread safe, you have to go for the Queued connection.

Just try both options.. and see what works for you.. Or specify nothing and let qt decide. Qt decides upon matching thread contexts. Which should work just fine here.

Cheers!

Johannes

franco.amato
9th December 2009, 19:53
Any. I suggest where you create the sounddata. If your sounddata is more generic than just data being played back, I suggest to set it up when you prepare it for playback. But then you will have to remove the connection after playback ended, to avoid multiple connections, when you play it back again. I don't know enough about the layout of your program to help you here..

BTW: As the connect function is a static function of the QObject class, you only need to write "QObject::" in front of connect, if you are outside a QObject class.. Inside you can just write "connect(..)". But I showed you QObject::connect .. to underline that you can call this from everywhere.


That depends on your callback library. Some execute the callback within the thread context of your application (synchronized) and some don't (async). Perhaps you can specify that when you set the callback. When the callback is already synchronized it doesn't make a lot of sense to go through qt's message system again (=>direct connection). Unless the callback needs to return very quickly and you want to do something big in your connected slot. If the callback is called asynchronously (in the threadcontext of the libraray) and you want to access GUI elements or something else that is not thread safe, you have to go for the Queued connection.

Just try both options.. and see what works for you.. Or specify nothing and let qt decide. Qt decides upon matching thread contexts. Which should work just fine here.

Cheers!

Johannes

The callback is executed when a playing file finish to play.
In this case I have to stop a QTimer that I use to determine the "pcm position" within the
audio data ( this to move a timeline ). So I think is not necesary to remove the connection. Because if a file is not playing the callback is never called. ( how I remove a callback?? ).

I don't exactly know if the callback is sync or async, I have to ask to the fmod community for that.
If is async I have to use the Queued connections flag right?

Thank you

JohannesMunk
9th December 2009, 21:46
You didn't get me.

If you set up the signal slot connection whenever you START playing the sounddata on an Audiodevice there will be multiple signal-slot-connections, if you play the same sound multiple times and you don't remove the connection with QObject::disconnect after play back stopped.

If you set it up only once when you create the sound everything is fine.

Callback: No need to ask. Just try what works. You can't break anything.

Good luck with your project!

Johannes

franco.amato
9th December 2009, 23:43
You didn't get me.

If you set up the signal slot connection whenever you START playing the sounddata on an Audiodevice there will be multiple signal-slot-connections, if you play the same sound multiple times and you don't remove the connection with QObject::disconnect after play back stopped.

If you set it up only once when you create the sound everything is fine.

Callback: No need to ask. Just try what works. You can't break anything.

Good luck with your project!

Johannes

Thank you very much Johannes for your precious help.

Best Regards

JohannesMunk
9th December 2009, 23:51
You are welcome!

franco.amato
21st January 2010, 18:05
You are welcome!

Hi I have a new problem. Some posts ago you explained to me how to connect a signal to a slots of 2 "unknow" objects, eather one is part of the other.
You gave to me this clear example:



AudioDevice* ad = new AudioDevice();
...
SoundData* sd = new SoundData();
...
QObject::connect(ad,SIGNAL(soundStopped()),sd,SLOT (someSLOT()));


Supposing ad (or object A in general has a slider)
I would connect the "valueChanged(int)" signal of the QSlider sl that's part of the object A with a slot that's part of the object B

Something like this:


AudioDevice* ad = new AudioDevice(); //ad has a QSlider sl
...
SoundData* sd = new SoundData(); //sd has a slot slot_sd(int);
...
QObject::connect(ad->sl,SIGNAL(valueChanged(int)),sd,SLOT(slot_sd(int)) );


Is it possible? :confused:
Best Regards