AudioSampleBuffer Playback Issues

Hi everyone! I’m new to JUCE and am trying to build a simple Audio Sample Playback plugin to learn the ropes a little bit. In my PluginProcessor.h I store a unique_ptr to an AudioSampleBuffer, set that pointer once I read the contents of a file in the PluginProcessor ctor, and then copy the contents (sample by sample) to the buffer passed into processBlock().

When I run the plugin, things seem to be working properly except that the audio playback is extremely glitchy and sounds terrible compared to how the sample should sound. Any help on this would be much appreciated!

Welcome to the forum!

Please show your code so we can better understand the issue. Playing the contents of an AudioBuffer should be pretty trivial to get right, but there are of course some details that can be wrong. (There’s also no need to use an AudioBuffer via a unique_ptr, it works fine as a value type. That however likely isn’t causing your playback issue.)

Thanks for the response, Xenakios! Attached is a screenshot of my code:

You are probably advancing the file buffer read position twice or more because your outer loop iterates over the plugin channels. Try inverting the looping order so that the outer loop iterates the samples and the inner loop iterates over the channels and only advance the read position when you advance the sample loop.

Something like :

for (int i = 0; i < buffer.getNumSamples(); ++i)
		{
			for (int j = 0; j < buffer.getNumChannels(); ++j)
			{
				int chantoread = j % m_fileBuffer.getNumChannels();
				buffer.setSample(j, i, 0.1*m_fileBuffer.getSample(chantoread,m_filePos));
			}
			++m_filePos;
			if (m_filePos >= m_fileBuffer.getNumSamples())
				m_filePos = 0;
		}
2 Likes

Ah yes that solved it, thank you!

Hi @Xenakios,

I’ve implemented what you’ve recommended but I’m not getting any audio out, any ideas?

::prepareToPlay

numberofChannels = round(getNumInputChannels());
sampleRate = (float)getSampleRate();
bufferSize = getBlockSize();

MemoryInputStream inputStream(BinaryData::country_sample_wav, BinaryData::country_sample_wavSize, false);
WavAudioFormat wavFormat;
AudioFormatReader* mFormatReader = wavFormat.createReaderFor (&inputStream, false);

AudioBuffer<float> wavBuffer(mFormatReader->numChannels, mFormatReader->lengthInSamples);
mFormatReader->read(&wavBuffer, 0, wavBuffer.getNumSamples(), 0, true, true);

::processBlock

for (int i = 0; i < bufferSize; i++)
{
    for (int j = 0; j < numberofChannels; j++)
    {
        int chantoread = j % wavBuffer.getNumChannels();
        buffer.setSample(j, i, 0.1*wavBuffer.getSample(chantoread,m_filePos));
    }
        
    if (m_filePos++ >= wavBuffer.getNumSamples())
    {
        m_filePos = 0;
    }
}

The first problem I see in the code is that you declare the wavBuffer as a local variable in prepareToPlay. So, it won’t be the same wavBuffer your processBlock is going to use.

Hi @Xenakios,

Thank you for the quick reply! I didn’t think this would be an issue as I declared

AudioBuffer<float> wavBuffer;

In the header - am I declaring this correctly?

Yes, the header/class member declaration is correct, but you appearing to be redeclaring a local buffer of the same name in your prepareToPlay method.

Consider adjusting your warning levels. A good compiler should warn you about shadowing a variable.
e.g.
-Wall, -Wshadow-all

@Xenakios sorry for more nooby questions, but when I remove the

AudioBuffer<float>

class declaration from inside ::prepareToPlay I get thrown the error

Type 'AudioBuffer <float>' does not provide a call operator'

I assume this is because wavBuffer is not a callable function here, I am trying to figure out a way to call the AudioBuffer function without having to redeclare the variable but for the life of me I can’t figure it out. Thank you for the help by the way, it’s really appreciated.

Yes, the method you are looking for is setSize()

You should keep the wavBuffer declaration in the class and remove it from the prepareToPlay method, and change it to just do :

wavBuffer.setSize(mFormatReader->numChannels, mFormatReader->lengthInSamples);

@daniel and @Xenakios thank you again, unfortunately I’m still not getting any audio out :frowning:

I have also declared this variable and pointer privately in the header, but I’m thinking that I didn’t need to as they are only needed in the scope of ::prepareToPlay?

AudioFormatManager mFormatManager;
AudioFormatReader* mFormatReader {nullptr};

I’m stepping through breakpoints in ::prepareToPlay and all the variables and pointers to memory seem to be assigned just fine. Inside ::processBlock, the wavBuffer’s allocated data is now present (thank you both again!), however the juce::AudioBuffer is allocated 0 bytes, could this be the problem?

It is an indicator, that your audio file has the length of 0.
Did you actually verify, that mFormatReader is not nullptr?

next thing is, if createReaderFor() succeeds, it will take ownership of the inputStream. That’s a bad thing here, because it is on the stack, so you will have a double delete.
It all looks pretty hairy.

May I ask, what you actually want to build?
It seems there are easier solutions than what you are trying right now, or that this approach won’t suit your goal…

@daniel yes I have just done so like this inside ::prepareToPlay

if (mFormatReader != nullptr)
{
    wavBuffer.setSize(mFormatReader->numChannels, mFormatReader->lengthInSamples);
    mFormatReader->read(&wavBuffer, 0, wavBuffer.getNumSamples(), 0, true, true);
}

I’m planning to build binaural audio plugin that convolves impulse responses with incoming audio. At the moment I’m trying to load a .wav file into AudioSampleBuffer (and play it to check it’s stored ok), with the intention to eventually build an array of AudioSampleBuffers filled with very short impulse responses.

Ok, so did you verify that this condition is actually hit?

To playback the audio I would suggest to use an AudioSource, which takes care of the position and how to slice the audio in playable buffers.

For instance use an AudioFormatReaderSource and call getNextAudioBlock() in each processBlock() call.

// member:
AudioFormatReaderSource* source = nullptr;

void prepareToPlay (double sampleRate, int estimatedSamplesPerBuffer) override
{
    auto* inputStream = new MemoryInputStream (BinaryData::country_sample_wav, BinaryData::country_sample_wavSize, false);
    WavAudioFormat format;
    auto* reader = format.createReaderFor (inutStream, true);  // takes ownership
    jassert (reader);

    if (reader)
    {
        source = new AudioFormatReaderSource (reader, true);  // takes ownership
        source->prepareToPlay (estimatedSamplesPerBuffer, sampleRate);
    }    
}

void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
{
    if (source == nullptr)
    {
        buffer.clear();
        return;
    }

    AudioSourceChannelInfo info (&buffer, 0, buffer.getNumSamples());
    source->getNextAudioBlock (info); // only ok because of MemoryInputStream!
}

later you might change that to load into a buffer and actually work with that

1 Like

@daniel Thank you so so much for such an in depth response at this kind of time! All working perfectly. I see what you mean about the importance of taking ownership of streams

Hello Rishdosh,
I have done same as you like load file when any button pressed, then modify ProcessBlock function as above, but what should we have to keep in PrepareToPlay function?.
I tried without keeping anyting, its not worked.