This tutorial shows how to play and loop audio stored in an AudioSampleBuffer object. This is a useful basis for sampling applications that manipulate recorded audio data.
Level: Intermediate
Platforms: Windows, macOS, Linux
Classes: AudioBuffer, AudioFormatReader, AudioAppComponent
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 for this tutorial allows the user to open a sound file, read the whole file into an AudioSampleBuffer object, and play it in a loop. In Tutorial: Build an audio player we played sound files using an AudioFormatReaderSource object connected to an AudioTransportSource object to play the sound. Looping is possible using this method by enabling enabling the AudioFormatReaderSource object's looping flag — using the AudioFormatReaderSource::setLooping() function.
All of the code relevant to the discussion in this tutorial is in the MainContentComponent
class of the demo project.
There are many cases where it is probably better to use the built-in classes for sound file playback. There may be occasions where you need to do this yourself and this tutorial gives an introduction to some of the techniques. Sampler applications commonly load the sound file data into memory like this, especially when the sounds are relatively short (see the SamplerSound class for an example). Synthesising sounds can also be achieved by storing a wavetable in an AudioSampleBuffer object and looping it at an appropriate rate to produce the required musical pitch. This is explored in Tutorial: Wavetable synthesis.
This tutorial also highlights some of the potential multi-threading issues you may encounter when combining access to files, and audio processing on the audio thread. Some of these problems seem simple on the surface but often require carefully applied techniques in order to avoid crashes and audio glitches. These techniques are explored further in Tutorial: Looping audio using the AudioSampleBuffer class (advanced).
The demo project limits the length of the sound file you can load to less than 2 seconds. This limit is rather arbitrary, but this is broadly for two reasons:
Regarding point 1: if we exceed the amount of physical memory the computer has, it may start to use virtual memory (that is, secondary storage such as a hard drive). This rather defeats the purpose of loading the data into memory in the first place! Of course the operation may just fail on some devices if it runs out of memory.
Regarding point 2: we keep the example simple by loading the audio data directly in, after the FileChooser::browseForFileToOpen() function has returned the file selected by the user. This means that the message thread will be blocked until all of the audio has been read in from disk into the AudioSampleBuffer object. Even with short sounds we should really do this on a background thread to keep the user interface as responsive as possible for the user. For long sounds the delay and unresponsiveness will be very noticeable. Adding another (background) thread would add to the complexity of this example. See Tutorial: Looping audio using the AudioSampleBuffer class (advanced) for example of how to load files on a background thread in this way.
When the user clicks the Open... button they are presented with a file chooser. The whole file is then read into an AudioSampleBuffer member fileBuffer
in our MainContentComponent
class.
getNextAudioBlock()
function will be called on the audio thread while we are still within the call to the Button::onClick lambda function (which will have called this openButtonClicked()
function from the message thread).reader
pointer is not a nullptr
value on the next line.fileBuffer
member using the AudioFormatReader::read() function. The arguments are:position
member to zero.In the getNextAudioBlock()
function the appropriate number of samples is read from our fileBuffer
AudioSampleBuffer member and written out the the AudioSampleBuffer object in the AudioSourceChannelInfo struct.
While reading the audio data from the file we keep track of the current read position using the position
member (being careful to update it after all the channels of the audio have been processed for the specified block of samples):
outputSamplesRemaining
variable stores the total number of samples that the getNextAudioBlock()
function needs to output taking a copy from the AudioSourceChannelInfo struct. We use this to check if we need to exit the while()
loop that starts on the next line.while()
loop we need to output the smaller of the remaining samples for this call to the getNextAudioBlock()
function and the remaining samples in the buffer — using the jmin() function. If this is less than the total number of samples for this call to the getNextAudioBlock()
function, then there will be one more pass of the while()
loop, before exiting.outputSamplesRemaining
variable.outputSamplesOffset
by the same amount in case we have another pass of the while()
loop.position
member by the same amount too.position
member reached the end of the fileBuffer
AudioSampleBuffer object and reset it to zero to form the loop if necessary.As discussed previously, this tutorial avoids multithreading issues by shutting down and restarting audio each time the user clicks the Open... button. But what if we didn't do this — what could happen? There many things that could go wrong, all of which have to do with the fact that both the getNextAudioBlock()
and openButtonClicked()
functions could be running at the same time in different threads. Here are some examples:
getNextAudioBlock()
function could be interrupted by code in the openButtonClicked()
function. Suppose this happens just after [11] and that the openButtonClicked()
function has just reached [4]. The buffer might be resized to be shorter than it was but we already calculated our starting point a few lines earlier. This could lead to a memory access error and the application could crash.getNextAudioBlock()
function could be interrupted while calling the AudioSampleBuffer::copyFrom() function. Again depending in the implementation of this we could end up accessing memory that we shouldn't.Two seconds of stereo audio at 44.1kHz would use 705,600 bytes in an AudioSampleBuffer object because there are:
float
type)Multiply these together and the result is: 2 x 2 x 44100 x 4 = 705600
In this tutorial we have introduced: