Why is there no simple way to play audio?

I know my title may seem a bit over the top but after weeks of messing around with JUCE I can’t help but feel like I’m fighting the library to work the way I want. Why must I either subclass an object and hack it to work with a bunch of other JUCE classes or accept that the at times opaque API will function how I’d like. I just want to play some audio files without using a bunch of classes and inheritance - without feeling like I’m working orthogonal to the JUCE workflow. In PortAudio for instance, I can in effect, register an audio device and a callback that talks to it. Maybe I’m just looking in all the wrong places but even the tutorials are built such that you must be within an AudioAppComponent subclass to use any of that functionality. I’ve worked out an example that works with my sequencer but it’s much too slow for what I want and I can’t do much to speed it up without being able to dive deeper into the interfaces I’m using. Sorry if this comes off as rant-y but I’m having lots of trouble finding useful docs and the tutorials seem very pushy on working completely contained within JUCE.

Is there a question here somewhere?

1 Like

Fair enough. I would like to know how to play audio without having to build a super JUCE-esque app. Essentially, can I just use the audio utilities/modules of JUCE to play an audio file in my own main or something analogous?

Here’s my play_audio method. For instance, it seems a bit opaque that I can’t explicitly call play.

 void AudioManager::play_audio()
{
    if (device_ && audio_source_) {
	    audio_source_->prepareToPlay(device_->getDefaultBufferSize(),
		device_->getCurrentSampleRate());

	player_->setSource(audio_source_);
	device_manager_.addAudioCallback(player_.get());
	audio_source_->setNextReadPosition(0);
	/* note the semicolon at the end of this line, we are preventing the callback from being removed until finished reading a file. */
	while (audio_source_->isPlaying());

	device_manager_.removeAudioCallback(player_.get());
}

}

So, what parts of JUCE do you actually want to use and why?

Firstly, thanks for taking some time to read this all. I want to use JUCE for graphics and audio. Essentially, a drum machine running on Linux or any desktop OS. I want to use the audio utilities to play audio and the graphical utilities to display some text and such.
I want to start with audio, and I simply can’t figure out how to play sound files in a more declarative fashion than giving all the data to a class to play for me.

addAudioCallback is your “play” and removeAudioCallback is your “stop”, at least if you are doing things like you are doing. You could implement your own AudioIODeviceCallback that stops and resumes the audio in some other manner.

You obviously must not do something like

while (audio_source_->isPlaying());

if this is a GUI application and that code is running in your main thread.

hmm…

looks a bit funny, does your app hang in that while loop while you’re waiting for the the player to finish?

If you’re trying to make an audio app, this way or registering and removing devices is strange. Ditch the playback objects and manage your own buffers. Register a callback at the start of the app and leave it open while your app is running.

You should really start with the audio app template it seems

No, the audio manager’s play method is invoked in a separate thread by my event system.

That doesn’t at first thought sound very good for integrating with JUCE…

Based on the responses here, it seems that I just have to play along with JUCE better to make it work. I’ll give it a shot.

I think it sounds like what you want is to ditch those transport source objects like in the juce audio demo. I was never a fan of those. I would just obtain and manage the actual buffer information myself… but perhaps you’re looking for a simpler solution than a more involved…

2 Likes

You’ll want to use the juce thread management so you can use the message queues and all the helpful utilities for interthread communication. If you plan to use only the audio & gui utilities individually without the Juce App Base, it is going to get hard and you’ll likely find you need more tools than you’re anticipating right now

Start by implementing your own custom AudioIODeviceCallback subclass, you can work with that pretty similarly like PortAudio works. So you do need to inherit and keep the callback object instance around, though. If that doesn’t sound like something you would like to do, you might want to ask yourself is C++ even a language you want to be working with.

A simple example of a callback class (I just made it fill the buffer with a sine wave) :

https://pastebin.com/xAb0XD2K

2 Likes

You guys have been quite helpful. I’m going to try using one of the starter templates provided to see if that alleviates some issues, and thanks for the code samples. Also thanks Jake_Penn for the ideas about ditching the transport source which is part of what I want to do.

With the above audio callback class I posted, if you really wanted to, you could have a main() like :

int main()
{
     AudioDeviceManager man;
     MyAudioCallback cb;
     man.initialise(0, 2, nullptr, true);
     man.addAudioCallback(&cb);
     Thread::sleep(1000);
     man.closeAudioDevice();
     return 0;
}   

But obviously that is something that is going to work only as the crudest example to be run from the command line, nothing to do with a real application…

Great snippet. Let me add one thing, even if you don’t create a GUI, you should add the line at the beginning of your main():

ScopedJuceInitialiser_GUI foo;

This is a RAII pattern, that will trigger the destruction of message queue, singletons, the AudioDeviceManager etc. when the scope of main() is unrolled. Otherwise you will likely experience leaks at shutdown.

2 Likes

Yeah, I didn’t thoroughly test the snippet but it seemed to run OK as-is on macOs.