Table of Contents
- Getting started
- The demo project
- Helpful classes
- Putting it together
- Adding pause functionality
- See also
This tutorial covers how to open and play sound files. This includes some important classes for handling sound files in JUCE.
Platforms: Windows, Mac OS X, Linux
Download the demo project for this tutorial here: tutorial_playing_sound_files.zip. Unzip the project and open it in your IDE.
If you need help with this step, see Tutorial: Getting started with the Projucer.
The demo project presents a three-button interface for controlling the playback of a sound file. The three buttons are:
- A button to present a file chooser to the user for them select the sound file.
- A button to play the sound.
- A button to stop the sound.
The interface is shown in the following screenshot:
While we can generate audio sample-by-sample in the
getNextAudioBlock() of the Audio Application template, there are some built-in tools for generating and processing audio. These allow us to link together high-level building blocks to form powerful audio applications without having to process each and every sample of audio within our application code (JUCE does this on our behalf). These building blocks are based on the AudioSource class. In fact, if you have followed any of the tutorials based on the AudioAppComponent class — for example, Tutorial: Simple synthesis (noise) — then you have been making use of the AudioSource class already. The AudioAppComponent class itself inherits from the AudioSource class and, importantly, contains an AudioSourcePlayer object that streams the audio between the AudioAppComponent and the audio hardware device. We can simply generate the audio samples directly in the
getNextAudioBlock() function but we can instead chain a number of AudioSource objects together to form series of processes. We make use of this feature in this tutorial.
JUCE provides number of tools for reading and writing sound files in a number of formats. In this tutorial we make use of several of these, in particular we use the following classes:
- AudioFormatManager: This class contains a list of audio formats (such as WAV, AIFF, Ogg Vorbis, and so on) and can create suitable objects for reading audio data from these formats.
- AudioFormatReader: This class handles the low-level file reading operations on the audio file and allows us to read audio in a consistent format (generally this means arrays of
floatvalues). When an AudioFormatManager object is asked to open a particular file, it creates instances of this class.
- AudioFormatReaderSource: This is a subclass of the AudioSource class. It can read audio data from an AudioFormatReader object and render the audio via its
- AudioTransportSource: This class is another subclass of the AudioSource class. It can control the playback of an AudioFormatReaderSource object. This control includes starting and stopping the playback of the AudioFormatReaderSource object. It can also perform sample rate conversion and it can buffer audio ahead of time if we wish.
We will now bring together these classes along with suitable user interface classes to make our sound file playing application. It is useful at this point to think about the various phases — or transport states — of playing an audio file. Once the audio file is loaded we can consider these four possible states:
- Stopped: Audio playback is stopped and ready to be started.
- Starting: Audio playback hasn't yet started but it has been told to start.
- Playing: Audio is playing.
- Stopping: Audio is playing but playback has been told to stop, after this it will return to the Stopped state.
To represent these states, we create an
enum within our
In the constructor for our
MainContentComponent class, we configure the three buttons:
Notice in particular that we disable the Play and Stop buttons initially. The Play button is enabled once a valid file is loaded. We can see here that we have added our
MainContentComponent object as a listener for each of these three buttons (see Tutorial: Listeners and broadcasters). We also initialise our transport state in the constructor's initialiser list.
In addition to the three TextButton objects we have four other members of our
MainContentComponent constructor we need to initialise the AudioFormatManager object to register a list of standard formats :
As a minimum this will enable the AudioFormatManager object to create readers for the WAV and AIFF formats. Other formats may be available depending on the platform and the options enabled in the
juce_audio_formats module within the Projucer project as shown in the following screenshot:
MainContentComponent constructor we also add our
MainContentComponent object as a listener  to the AudioTransportSource object so that we can respond to changes in its state (for example, when it stops):
- The function name is
addChangeListener()in this case, rather than simply
addListener()as it is with many other listener classes in JUCE.
When changes in the transport are reported, the
changeListenerCallback() function will be called. This will be called asynchronously on the message thread:
You can see that this just calls a member function
The changing of the transport state is localised into this single function
changeState(). This helps keep all of the logic for this functionality in one place. This function updates the
state member and triggers any changes to other objects that need to take place when in this new state.
- More experienced readers may wish to use the state design pattern as an alternative way of structuring this code.
- : When the transport returns to the Stopped state it disables the Stop button, enables the Play button, and resets the transport position back to the start of the file.
- : The Starting state is triggered by the user clicking the Play button, this tells the AudioTransportSource object to start playing. At this point we disable the Play button too.
- : The Playing state is triggered by the AudioTransportSource object reporting a change via the
changeListenerCallback()function. Here we enable the Stop button.
- : The Stopping state is triggered by the user clicking the Stop button, so we tell the AudioTransportSource object to stop.
The audio processing in this demo project is very straightforward: we simply hand off the processing to the AudioTransportSource object by passing it the AudioSourceChannelInfo struct that we have been passed via the AudioAppComponent class:
Notice that we check if there is a valid AudioFormatReaderSource object first and simply zero the output if not (using the convenient AudioSourceChannelInfo::clearActiveBufferRegion() function). The AudioFormatReaderSource member is stored in a ScopedPointer object because we need to create these objects dynamically based on the user's actions. It also allows us to check for
nullptr for invalid objects.
We also need to remember to pass the
prepareToPlay() callback to any other AudioSource objects we are using:
releaseResources() callback too:
buttonClicked() function we call some sensibly-named functions to perform the action:
To open a file we pop up a FileChooser object in response to the Open... button being clicked:
- : Create the FileChooser object with a short message and allow the user to select only
- : Pop up the FileChooser object — note that this uses a modal loop which is not supported on all platforms.
- : If
if()will succeed if the user actually selects a file (rather than cancelling)
- : The AudioFormatManager::createReaderFor() function is used attempt to create a reader for this particular file. This will return the
nullptrvalue if it fails (for example the file is not an audio format the AudioFormatManager object can handle).
- : We create a new AudioFormatReaderSource object using the reader we just created. The second argument
trueindicates that we want the AudioFormatReaderSource object to manage the AudioFormatReader object and delete it when it is no longer needed. We store the AudioFormatReaderSource object in a temporary ScopedPointer object to avoid deleting a previously allocated AudioFormatReaderSource prematurely on subsequent commands to open a file.
- : The AudioFormatReaderSource object is connected to our AudioTransportSource object that is being used in our
getNextAudioBlock()function. In case the sample rate of the file doesn't match the hardware sample rate we pass this in as the fourth argument, which we obtain from the AudioFormatReader object. See Notes for more information on the second and third arguments. The AudioTransportSource source will handle any sample rate conversion that is necessary.
- : The Play button is enabled so that the user can click on it.
- : Since the AudioTransportSource should now be using our newly allocated AudioFormatReaderSource object we can safely store the AudioFormatReaderSource object in our
readerSourcemember. (As mentioned in Processing the audio above.) To do this we must transfer ownership from the local
newSourcevariable by using ScopedPointer::release().
- Storing the newly allocated AudioFormatReaderSource object in a temporary ScopedPointer object has the added benefit of being exception-safe. An exception could be thrown during the function call AudioTransportSource::setSource(), in which case the ScopedPointer object will delete the AudioFormatReaderSource object that is no longer needed. If a raw pointer had been used at this point to store AudioFormatReaderSource object then there could be a memory leak since the pointer would be left dangling if the exception is thrown.
Since we have already set up the code to actually play the file, we need only call our
changeState() function with the appropriate argument to play the file. When the Play button is clicked, we do the following:
Stopping the file is similarly straightforward, when the the Stop button is clicked:
- Change the third (
filePatternsAllowed) argument when creating the FileChooser object to allow the application to load AIFF files too. The file patterns can be separated by a semicolon so this should be
"*.wav;*.aif;*.aiff"to allow for the two common file extensions for this format.
We will now walk through some steps to add a pause functionality to the application. Here we will make the Play button become a Pause button while the file is playing (instead of just disabling it). We will also make the Stop button become a Return to zero button while the sound file is paused.
First of all we need to add two states Pausing and Paused to our
changeState() function needs to handle the two new states and the code for the other states needs to be updated too:
We enable and disable the buttons appropriately, and update the button text correctly in each state.
Notice that we actually stop the transport when asked to pause in the Pausing state. In the
changeListenerCallback() function, we need to change the logic to move to the correct state depending on whether a pause or stop request was made:
We need to change the code when the Play button is clicked:
And when the Stop button is clicked:
And that's it: you should be able to build and run the application now.
- The source code for this modified version of the application can be found in the
MainComponent_02.cppfile in the
Sourcedirectory of the demo project.
- Add a Label object to the interface that displays the current time position of the AudioTransportSource object. You can use the AudioTransportSource::getCurrentPosition() function to obtain this position. You will also need to make the
MainContentComponentclass inherit from the Timer class and perform periodic updates in your
timerCallback()function to update the label. You could even use the RelativeTime class to convert the raw time in seconds to a more useful format in minutes, seconds, and milliseconds.
- The source code for this exercise can be found in the
MainComponent_03.cppfile in the
Sourcedirectory of the demo project.
In this tutorial we have introduced the reading and playing of sound files. In particular we have covered the following things:
- Using the AudioFormatManager class to create AudioFormatReader objects for common audio formats.
- Using various AudioSource classes to create and connecting them together. In particular:
- We also looked one way of managing the state of audio file playback.
The second and third arguments to the AudioTransportSource::setSource() function allow you to control look ahead buffering on a background thread. The second argument is the buffer size to use and the third argument is a pointer to a TimeSliceThread object, which is used for the background processing. In this example we use a zero buffer size and a
nullptr value for the thread object, which is the default.
Generated on Fri Jan 12 2018 09:51:15 for JUCE by 1.8.13