Combining my audio class and my GUI class


#1

Hi.

I had initially made an AudioAppComponent that plays some sounds (well I guess that's obvious). Then I wanted to have a graphical interface. What I did is that I made another project and I made the GUI in it, independently from the audio part. It is not complete but it contains the essential graphical methods that I would need.

Now I have adapted the AudioAppComponent into an AudioSource class and bundled all my GUI in a Component class named GUIComponent, so I can instantiate both the encapsulating MainComponent.

Problem is, I don't know how I'm supposed to make the audio part and the GUI part interact between them. I've added some methods in the GUIComponent to be used by the containing class. However it should also be able to call some method in the containing class when certain types of events happen, and that's the part that is bothering me.

What procedure would you recommend ?


#2

Have a read here for some ideas: http://www.juce.com/forum/topic/whats-best-practice-gui-change-notification


#3

I tried using this class made by haydxn : http://www.juce.com/forum/topic/asynccallback
I just change it a little by adding a parameter that can be taken in the trigger method


template <class OwnerClass, class T>
class AsyncCallback : public AsyncUpdater
{
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AsyncCallback);
public:
    typedef void (OwnerClass::*CallbackFunction) (T);
    AsyncCallback(OwnerClass& ownerInstance, CallbackFunction functionToCall)
        : owner(ownerInstance),
        function(functionToCall)
    {
    }
    ~AsyncCallback()
    {
        cancel();
        function = nullptr; // Mark as invalid to avoid further triggers during destruction
    }
    void cancel()
    {
        cancelPendingUpdate();
    }
    void trigger(T _argument)
    {
        if (function != nullptr)
        {
            argument = _argument;
            triggerAsyncUpdate();
        }
    }
    void triggerSynchronously()
    {
        if (function != nullptr)
        {
            cancel();
            handleAsyncUpdate();
        }
    }
    void handleAsyncUpdate() override
    {
        if (function != nullptr)
        {
            (owner.*(function)) (argument);
        }
    }
private:
    OwnerClass& owner;
    CallbackFunction function;
    T argument;
};

In my main component I have the following code:


void MainComponent::mouseDown(const MouseEvent &e){
    guiComponent.async_changeColour(1);
    guiComponent.async_changeColour(0);    
}

Basically, the async_changeColour method calls the trigger method of haydxn's class.

Sadly, there is a problem.
The problem is, I don't really know why, but only the last call works. In my example, async_changeColour(0) fulfills its purpose while async_changeColour(1) seems to do nothing. When I swap them or when I remove async_changeColour(0), then async_changeColour(0) works.

I thought it may have been because of the first thing pending in the AsyncUpdater, so I tried the following


void MainComponent::mouseDown(const MouseEvent &e){
    guiComponent.threadSafe_changeColour(1);
    _sleep(1000);
    guiComponent.threadSafe_changeColour(0);
}

But then it still did only the first, only after waiting for 1 second.

Please tell me if I got it all wrong.

 


#4

This is the expected behaviour with AsyncUpdater: all calls to threadSafe_changeColour are first collected and then executed a single time later on the main message loop with the last used argument - in your case '0'. The main message loop can only be executed after the mouseDown method has finished. Therefore, the _sleep(1000) will only delay the execution of the main message loop so doesn't change anything. See for example the comment in juce_AsyncUpdater.h:

/**
    Has a callback method that is triggered asynchronously.
    This object allows an asynchronous callback function to be triggered, for
    tasks such as coalescing multiple updates into a single callback later on.
    Basically, one or more calls to the triggerAsyncUpdate() will result in the
    message thread calling handleAsyncUpdate() as soon as it can.
*/

If you, for example, change your code to the following:


void MainComponent::mouseDown(const MouseEvent &e){
   static int testColour=0;
   guiComponent.threadSafe_changeColour(testColour++);
}

the colour should change everytime the user clicks.


#5

Thank you for your answer.

Yet, I'd like to execute the two calls in a single passing through mouseDown, because the expected result is to change the colour of two components which are identified by 0 and 1.

Could I do that with AsyncUpdater or would I have to choose an entirely different way ?


#6

Just a logical question :

Why the AudioAppComponent class inherits the Component class if I can't add GUI elements directly on it?

 


#7

You can add GUI elements directly on to it. For example, you can add the following code in the constructor:


MainContentComponent ()
{
    addAndMakeVisible (toggleButton = new ToggleButton ("new toggle button"));
    addAndMakeVisible (textButton = new TextButton ("new button"));

    setSize (600, 400);
}

where toggleButton and textButton are ScopedPointers.


#8

Ok. 

When I say "directly" I mean directly on MainContentComponent at design time in Introjucer.   


#9

You can do this as well, albeit it's a bit involved: when you create a new Audio Applicaton in the Introjucer, immedietely delete the MainComponent.cpp, then use "Add new GUI Component..." and make sure that parent class under the class tab of the GUI editor is changed to "public AudioAppComponent". You will then need to add the AudioAppComponent callbacks to the UserMethods section of your component header. For example, like this:

 //[UserMethods]     -- You can add your own custom methods in this section.
 void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
 void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override;
 void releaseResources() override; 
 //[/UserMethods]

Likewise, add the implementations of them in the MiscUserCode section of .cpp file

//[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
void MainContentComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
}
 
void MainContentComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
}
void MainContentComponent::releaseResources()
{
}
//[/MiscUserCode]

In your Main.cpp, you will also need to add the header of your component to your includes and change the "setContentOwned" call accordingly:

setContentOwned (new MainContentComponent(), true);

#10

Thanks for the complete and detailed response.
I really appreciate it.