Issue with simple audio player plugin

Hi all,
I am trying to port into a plugin the juce example for audio applications about a simple file player: https://docs.juce.com/master/tutorial_playing_sound_files.html

I want to have an editor subcomponent dedicated to the buttons for opening and playing the files and I want to use a value tree state. I can’t understand where and how to associate properly to a button a function when the button is clicked.

I have created a GUI subcomponent in a separate file PanelFilePlayer.h:

#pragma once
#include "../JuceLibraryCode/JuceHeader.h"

class PanelFilePlayer : public Component
{
public:

    PanelFilePlayer (AudioProcessorValueTreeState& valueTreeState)
    {

        addAndMakeVisible (openButton);
        openButton.setButtonText ("Open...");
        openButton.onClick = [this] { openButtonClicked(); };
        
        openButtonAttachment.reset (new ButtonAttachment (valueTreeState, "open_button_panel_file_player", openButton));
    }
    
    ~PanelFilePlayer(){
        openButtonAttachment.reset();
    }
    
    void paint (Graphics& g) override
    {
        g.fillAll(Colours::darkblue);
        
        Rectangle<int> PanelFilePlayerArea (getLocalBounds());
        g.setColour (Colours::whitesmoke);
        g.drawRect (PanelFilePlayerArea, 3.0f);
    }
    
    void resized() override
    {
        openButton .setBounds (10, 10, getWidth() - 20, 20);    
    }
    
private:
    typedef AudioProcessorValueTreeState::ButtonAttachment ButtonAttachment;
    TextButton openButton;
    std::unique_ptr<ButtonAttachment> openButtonAttachment;
};

The PanelFilePlayer.h is included in the PluginEditor.h. Now, I wonder where the function openButtonClicked() should be placed?
Ideally would like to place all functions related to the buttons (including those for thread safety) in a separate .h file (let’s call it FilePlayer.h). Such functions however are related to the audio system so somehow they should interact with the PluginProcessor.cpp.

In the file PluginProcessor.cpp I put these sections:

    MyFilePlayerPluginAudioProcessor::MyFilePlayerPluginAudioProcessor()
: AudioProcessor (BusesProperties().withOutput ("Output", AudioChannelSet::stereo(), true)),
parametersPanelFilePlayer (*this, nullptr, Identifier ("ParametersPanelFilePlayer"), createParameterLayoutPanelFilePlayer())
{
    openButtonParameterPanelFilePlayer = parametersPanelFilePlayer.getRawParameterValue ("open_button_panel_file_player");
}


AudioProcessorValueTreeState::ParameterLayout MyFilePlayerPluginAudioProcessor::createParameterLayoutPanelFilePlayer()
{
    std::vector<std::unique_ptr<RangedAudioParameter>> params;

    auto openButtonParam = std::make_unique<AudioParameterBool> ("open_button_panel_file_player", // parameterID
                                                                 "Open Button",                        // parameter name
                                                                 false);                                // default value
    
    params.push_back(std::move(openButtonParam));
    return {params.begin(), params.end()};
}

void MyFilePlayerPluginAudioProcessor::updateParameters()
{
	//openButtonParameterPanelFilePlayer->onClick = [this] { openButtonClicked(); }; 
	//This gives the error: No member named 'onClick' in 'std::__1::atomic<float>'
}

I am a bit lost at conceptual level… can anyone be so kind to tell me what I am doing wrong?

I’m not sure that an audio parameter is a good fit for something like an ‘open’ button (or at least, I can’t imagine a situation in which you might want to automate such a control).

Another approach to consider would be to have a public function in your processor which loads a file which is supplied as an argument (be careful to make this thread safe!). Then, in your editor, you can just set your open button’s onChange function to a lambda which displays a file chooser, and then immediately passes the selected file to that new function in your processor.

Thanks for replying reuk. I have understood, it is maybe non sense automatize the button play. I tried to follow your advice but I am stuck.

My file PanelFilePlayer.h now looks like this (just copied the relative parts of the tutorial):

    #pragma once
#include "../JuceLibraryCode/JuceHeader.h"

class PanelFilePlayer : public Component,
                        public ChangeListener
{
public:

    PanelFilePlayer (AudioProcessorValueTreeState& valueTreeState)
    {
        
        state = Stopped;
                    
        addAndMakeVisible (&openButton);
        openButton.setButtonText ("Open...");
        openButton.onClick = [this] { openButtonClicked(); };

        addAndMakeVisible (&playButton);
        playButton.setButtonText ("Play");
        playButton.onClick = [this] { playButtonClicked(); };
        playButton.setColour (TextButton::buttonColourId, Colours::green);
        playButton.setEnabled (false);

        addAndMakeVisible (&stopButton);
        stopButton.setButtonText ("Stop");
        stopButton.onClick = [this] { stopButtonClicked(); };
        stopButton.setColour (TextButton::buttonColourId, Colours::red);
        stopButton.setEnabled (false);

        addAndMakeVisible (&loopingToggle);
        loopingToggle.setButtonText ("Loop");
        loopingToggle.onClick = [this] { loopButtonChanged(); };

        addAndMakeVisible (&currentPositionLabel);
        currentPositionLabel.setText ("Stopped", dontSendNotification);
        
        globalGainAttachment.reset (new SliderAttachment (valueTreeState, "global_gain_panel_file_player", globalGainSlider));
        
        
        formatManager.registerBasicFormats();
        transportSource.addChangeListener (this);
    }
   
    
    ~PanelFilePlayer(){
        
        globalGainAttachment.reset();
    }
    
    
    void changeListenerCallback (ChangeBroadcaster* source) override
    {
        if (source == &transportSource)
        {
            if (transportSource.isPlaying())
                changeState (Playing);
            else
                changeState (Stopped);
        }
    }
    

    void updateLoopState (bool shouldLoop)
    {
        if (readerSource.get() != nullptr)
            readerSource->setLooping (shouldLoop);
    }

    void paint (Graphics& g) override
    {
        g.fillAll(Colours::darkblue);
        
        Rectangle<int> PanelFilePlayerArea (getLocalBounds());
        g.setColour (Colours::whitesmoke);
        g.drawRect (PanelFilePlayerArea, 3.0f);
    }
    
    void resized() override
    {
        auto sliderLeft = paramLabelWidth + 10;
        globalGainSlider.setBounds (sliderLeft, 200, getWidth() - sliderLeft - 10, 20);
        
        openButton          .setBounds (10, 10,  getWidth() - 20, 20);
        playButton          .setBounds (10, 40,  getWidth() - 20, 20);
        stopButton          .setBounds (10, 70,  getWidth() - 20, 20);
        loopingToggle       .setBounds (10, 100, getWidth() - 20, 20);
        currentPositionLabel.setBounds (10, 130, getWidth() - 20, 20);
    }
    
private:
    
    typedef AudioProcessorValueTreeState::SliderAttachment SliderAttachment;
    
    enum
    {
        paramControlHeight = 40,
        paramLabelWidth    = 80,
        paramSliderWidth   = 350
    };

    enum TransportState
    {
        Stopped,
        Starting,
        Playing,
        Stopping
    };

    void changeState (TransportState newState)
    {
        if (state != newState)
        {
            state = newState;

            switch (state)
            {
                case Stopped:
                    stopButton.setEnabled (false);
                    playButton.setEnabled (true);
                    transportSource.setPosition (0.0);
                    break;

                case Starting:
                    playButton.setEnabled (false);
                    transportSource.start();
                    break;

                case Playing:
                    stopButton.setEnabled (true);
                    break;

                case Stopping:
                    transportSource.stop();
                    break;
            }
        }
    }

    void openButtonClicked()
    {
        FileChooser chooser ("Select a Wave file to play...",
                             {},
                             "*.wav");

        if (chooser.browseForFileToOpen())
        {
            auto file = chooser.getResult();
            auto* reader = formatManager.createReaderFor (file);

            if (reader != nullptr)
            {
                std::unique_ptr<AudioFormatReaderSource> newSource (new AudioFormatReaderSource (reader, true));
                transportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate);
                playButton.setEnabled (true);
                readerSource.reset (newSource.release());
            }
        }
    }

    void playButtonClicked()
    {
        updateLoopState (loopingToggle.getToggleState());
        changeState (Starting);
    }

    void stopButtonClicked()
    {
        changeState (Stopping);
    }

    void loopButtonChanged()
    {
        updateLoopState (loopingToggle.getToggleState());
    }

    //==========================================================================        
    TextButton openButton;
    TextButton playButton;
    TextButton stopButton;
    ToggleButton loopingToggle;
    Label currentPositionLabel;

    AudioFormatManager formatManager;
    std::unique_ptr<AudioFormatReaderSource> readerSource;
    AudioTransportSource transportSource;
    
    TransportState state;
};

However, now I need to pass to the PluginProcessor.cpp all it is needed to play the file. The tutorial has these functions that I need to use inside the PluginProcessor.cpp

void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
{
    transportSource.prepareToPlay (samplesPerBlockExpected, sampleRate);
}

void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
    if (readerSource.get() == nullptr)
    {
        bufferToFill.clearActiveBufferRegion();
        return;
    }

    transportSource.getNextAudioBlock (bufferToFill);
}

void releaseResources() override
{
    transportSource.releaseResources();
}

The issue is: how can I pass from the Editor to the Processor the variable transportSource? That variable was declared in the file (which is a GUI subcomponent used by the Editor).
I know that it is not recommend to pass anything from the Editor to the Processor, but it is the other way around that needs to be followed.

State that should persist when the plugin editor is closed (like the playing file, file position, etc.) should live in the processor, rather than the editor.

That being said, I’m not sure it’s a good idea to use AudioTransportSource in a plugin. Normally, you’d want the plugin’s playback to be synced to the DAW, rather than having the plugin control its own playback.

Ok I see. Then, how can I simply load a file and play it in a plugin? I can’t find any other tutorial example. Do you or others have any source code I could look at? Any suggestion is very welcome

In any case, that is precisely what I want to do. I don’t care about the DAW host behaviour, I simply want to load and play/stop a file within a plugin.