This tutorial shows how to play and loop audio stored in an AudioSampleBuffer object using thread-safe techniques. A technique for loading the audio data on a background thread is also introduced.
Level: Advanced
Platforms: Windows, macOS, Linux
Classes: ReferenceCountedObject, ReferenceCountedArray, Thread, AudioBuffer
This tutorial leads on from Tutorial: Looping audio using the AudioSampleBuffer class. If you haven't done so already, you should read that tutorial first.
Download the demo project for this tutorial here: PIP | ZIP. Unzip the project and open the first header file in the Projucer.
If you need help with this step, see Tutorial: Projucer Part 1: Getting started with the Projucer.
The demo project implements similar behaviour to the demo project from Tutorial: Looping audio using the AudioSampleBuffer class. It allows the user to open an audio file that is loaded into a buffer and played in a loop. One major difference in this tutorial is that the audio system is kept running, rather than shutting it down each time we browse for a file. This is achieved by using some helpful classes for communicating between threads in a thread-safe manner.
You should recall in Tutorial: Looping audio using the AudioSampleBuffer class how we solved the potential problem of the audio thread and the message thread accessing potentially incomplete or corrupted data. Just before we browsed for a file we shut down the audio system. Then, once a file was selected, we opened the file and restarted the audio system. This is clearly an impractical and cumbersome method in a real application!
The ReferenceCountedObject class is a useful tool for passing messages and data between threads. Here, we store our AudioSampleBuffer object and the playback position in a ReferenceCountedObject class. To help with debugging, and to help illustrate how the class works, we also include name
member (although this isn't strictly necessary for the class to function):
The typedef
at the top of the class is an important part in implementing a ReferenceCountedObject subclass. Rather than storing our ReferenceCountedBuffer
object in a raw pointer, we store it in a ReferenceCountedBuffer::Ptr
type. It is this that manages the reference count of the object (incrementing and decrementing as necessary) and its lifetime (deleting the object when the reference count reaches zero). We can also store an array of ReferenceCountedBuffer
objects using the ReferenceCountedArray class.
In our MainContentComponent
class we store both an array and a single instance:
The buffers
member keeps hold of our buffers in the array until we are absolutely sure they are no longer needed by the audio thread. The currentBuffer
member holds the currently selected buffer.
Our MainContentComponent
class inherits from the Thread class:
This is used to implement our background thread. Our overridden Thread::run() function is as follows:
Here, we check whether there are any buffers to be freed, then our thread waits for 500ms or to be woken up (using the Thread::notify() function). Essentially, this means that the check will occur at least every 500ms. The checkForBuffersToFree()
function searches through our buffers
array to see if any buffers can be freed:
buffers
and the other will be in the local buffer
variable. The removed buffer will delete itself as the buffer
variable goes out of scope (as this will be the last remaining reference).Of course, we need to start the thread as our application starts, which we do in our MainContentComponent
constructor:
Our openButtonClicked()
function is similar to the openButtonClicked()
function from Tutorial: Looping audio using the AudioSampleBuffer class with some minor differences:
Here are the differences:
ReferenceCountedBuffer
class.To clear the current buffer we can just set its value to nullptr
:
Our getNextAudioBlock()
function is similar to the getNextAudioBlock()
function from Tutorial: Looping audio using the AudioSampleBuffer class except we need to access our current ReferenceCountedBuffer
object and the AudioSampleBuffer object it contains.
The important changes are:
currentBuffer
member. After this point in the function it doesn't matter if the currentBuffer
member is changed on another thread since we have taken a local copy. Note that we use a try lock here, so that the audio thread doesn't get stuck waiting to access the currentBuffer
in the case that another thread is currently modifying it.currentBuffer
member was null when we took a copy.ReferenceCountedBuffer
object.ReferenceCountedBuffer
object.This algorithm ensures that ReferenceCountedBuffer
objects aren't deleted on the the audio thread. It is not a good idea to allocate or free memory on the audio thread. The ReferenceCountedBuffer
objects will only be deleted on our background thread.
Our application still reads the audio data on the message thread. This is not ideal since this blocks the message thread and large files could take some time to load. In fact, we can also use our background thread to perform this task.
First, add the following member to the MainContentComponent
class:
Now change the openButtonClicked()
function to swap the full path of the file into this member. Swapping strings is not technically thread-safe, so we also need to take a lock to ensure that no other threads try to modify chosenPath
while this thread is using it.
Here we also wake up the background thread since we are going to call a function on the background thread to open the file.
Our run()
function should be updated as follows:
The checkForPathToOpen()
function checks the chosenPath
member by swapping it into a local variable. Again, swapping is not thread-safe, so we must take a lock before accessing chosenPath
.
If the pathToOpen
variable is an empty string then we know there isn't a new file to open. The remainder of the code in this function should be familiar to you.
Run the application again and it should still function correctly.
LoopingAudioSampleBufferAdvancedTutorial_02.h
file of the demo project.This tutorial has introduced some useful techniques for passing data between threads, especially in an audio application. After reading this tutorial you should be able to: