Loading Multiple Files Into Audio Player

Hey all,

I’m working on a standalone app which allows the user to load in audio files, play/pause/resume/stop the file, view a thumbnail of the waveform etc. I got started using the “Build an audio player” and “Draw audio waveform” tutorials which both worked great.

I have moved all of the code handling the loading, transport functionality, waveform drawing etc into it’s own class so that I can instantiate multiple instances.

Here is the class:

#pragma once

#include <JuceHeader.h>

class fileComponent  : public juce::AudioAppComponent, public juce::ChangeListener, private juce::Timer
{
public:
    fileComponent();
    ~fileComponent() override;

    enum TransportState
    {
        Stopped,
        Starting,
        Playing,
        Pausing,
        Paused,
        Stopping
    };
    
    void paint (juce::Graphics&) override;
    void resized() override;
    //==============================================================================
    void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
    void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override;
    void releaseResources() override;
    
    
    void changeListenerCallback (juce::ChangeBroadcaster* source) override;
    void changeState (TransportState newState);
    
    void openButtonClicked();
    void playButtonClicked();
    void stopButtonClicked();
    
    void transportSourceChanged();
    void thumbnailChanged();
    void paintIfNoFileLoaded (juce::Graphics& g, const juce::Rectangle<int>& thumbnailBounds);
    void paintIfFileLoaded (juce::Graphics& g, const juce::Rectangle<int>& thumbnailBounds);
    void timerCallback() override;



    
private:
    std::unique_ptr<juce::FileChooser> chooser;

    juce::AudioFormatManager formatManager;
    std::unique_ptr<juce::AudioFormatReaderSource> readerSource;
    juce::AudioTransportSource transportSource;
    TransportState state;
    juce::AudioThumbnailCache thumbnailCache;
    juce::AudioThumbnail thumbnail;
    
    juce::TextButton openButton;
    juce::TextButton playButton;
    juce::TextButton stopButton;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (fileComponent)
};

Here is the code relating to actually loading in files:

void fileComponent::openButtonClicked()
{
    chooser = std::make_unique<juce::FileChooser> ("Select a Wave file to play...",
                                                   juce::File{},
                                                   "*.wav");
    auto chooserFlags = juce::FileBrowserComponent::openMode
                      | juce::FileBrowserComponent::canSelectFiles;

    chooser->launchAsync (chooserFlags, [this] (const juce::FileChooser& fc)
    {
        //Get the file
        auto file = fc.getResult();
        //Check the file
        if (file != juce::File{})
        {
            //Make a reader for this file
            auto* reader = formatManager.createReaderFor (file);

            if (reader != nullptr)
            {
                //Make the Format Reader Source from the reader
                auto newSource = std::make_unique<juce::AudioFormatReaderSource> (reader, true);
                //Set the transport source
                transportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate);
                playButton.setEnabled (true);
                //Set source for the thumbnail
                thumbnail.setSource(new juce::FileInputSource (file));
                //Get rid of the Reader Source
                readerSource.reset (newSource.release());
            }
        }
    });
}

In the private section of my MainComponent.h file I have this:

    fileComponent fileComp1;
    fileComponent fileComp2;
    fileComponent fileComp3;

In the MainComponent.cpp file I addAndMakeVisible and call prepareToPlay, getNextAudioBlock, releaseResources and set their bounds in the appropriate places. When I run the app and load files into each of the objects, I can click play/pause/etc. but only fileComp3 actually generates audio. The waveforms and buttons are loaded for all of them.

I’m thinking the problem is coming from the fact that in “openButtonClicked()” function, when each new file is being loaded,

readerSource.reset (newSource.release());

is being called. Since it is using a std::unique_ptr the object it’s pointing to is getting released and the next file gets loaded, the final file is the only one that is actually still loaded. It doesn’t seem to matter what order I click open and load files in, only the final one works as intended. I’m struggling with fixing this issue and would love some help.

Thanks in advance

After some reading as well as asking ChatGPT I believe this issue is that I have the changeListenerCallback override and the timerCallback override in the fileComponent class. I’m thinking that maybe they should be overridden in the MainComponent class and then I need some way of managing which object is affected.

I don’t see any code where you actually add the content of the three files (I assume your intention is to play them simultaneously, o/w you wouldn’t need three file player instances.) I would guess it’s somewhere near the getNextAudioBlock you mention. Why don’t you put a breakpoint in the debugger near getNextAudioBlock and see what’s actally happening there?

ChatGPT is in the woods here…

Your fileComponent shouldn’t inherit AudioAppComponent, because each one comes with it’s own AudioDeviceManager. But you must not have more than one AudioDeviceManagers at a time.

Easiest workaround would be to have an AudioDeviceManager in your MainComponent and use that for the various AudioAppComponent:

private:
    juce::AudioDeviceManager audioManager;
    fileComponent comp1 {audioManager};
    fileComponent comp2 {audioManager};
    fileComponent comp3 {audioManager};

But there is another problem. With that approach the fileComponents are not synchronised, because adding the AudioIODeviceCallback (which is the SourcePlayer the AudioAppComponent provides) is added asynchronously. Same goes for the AudioTransportSource you are using. calling play/stop is also asynchronously, so it is indeterministic when the sample actually starts and stops. Plus a jitter of block size, which can go up to 40 ms or more depending on device type.

Instead better have one AudioSourcePlayer or AudioProcessorPlayer and code the pipeline from scratch.
For instance have ONE AudioTransportSource, if your app has a timeline with a transport. That could be fed either by an AudioProcessorGraph or MixerAudioSource and your bespoke player.

But now I assumed a bit too much, maybe you want to elaborate first what type of app you are planning…

Thanks for all the help everyone! Let me give some more details.

The goal of my program is to allow the user to load in multiple audio files, view information about each of them (such as length, sample rate, frequency spectrum content, loudness etc) and be able to listen to each one. It is not imperative that the playback for all of these be synced in any way as each fileComponent will have it’s own play/pause/stop buttons.

I was likely mistaken that fileComponent should inherit from AudioAppComponent as I don’t have much experience working with actual audio files in JUCE. I have mainly been working on FX and made this project based off the “Build and audio player” and “Draw audio waveforms” tutorials. It sounds like a better approach would be to pass in the AudioDeviceManager to each fileComponent object.

My question then would be do I still need an AudioFormatReaderSource and AudioTransportSource for each fileComponent? Should I make however many I need in the MainComponent based on how many fileComponent objects I need to make? Or should each fileComponent have its own in that class? I’ve tried reading through the documentation but I’m still a little confused as to what objects can be made global and what each fileComponent needs.

Thanks again!

Yes, you need an AudioTransportSource for each file if you want to be able to stop, pause and play them individually se documentation
https://docs.juce.com/develop/classAudioTransportSource.html

And each of these take an PositionableAudioSource…

Don’t overthink the class hierarchy at the start. First make it work with the individual building blocks and one file. When that’s working, continue with another file. Then you can start thinking about modularizing things and what should go in what class and what should be common to all etc.

Yeah I think you’re right, I’ll try to make a new project with some small scale experiments. I was hoping that doing the tutorials first would be enough but I guess I’m still missing some understanding about how everything works together.