Using the AudioBuffer tutorial method in processBlock()

Im basically wondering if the getNextAudioBlock() code (see below) from the tutorial “Looping audio using the AudioSampleBuffer class,” (https://www.juce.com/doc/tutorial_looping_audio_sample_buffer), can be used in processBlock(). Assuming that it is possible, since getNextAudioBlock() uses AudioSourceChannelInfo and processBlock() uses AudioBuffer, I think that’s where I’m having difficulty. I’ve tried so many different ways of using the AudioBuffer class, specifically with outputSamplesRemaining and outputSamplesOffset but nothing seems to work. I’ve been at this for days and can’t seem to find what I’m missing. I’m still new at this, so if this method doesn’t actually work at all for processBlock(), I’d really appreciate any input on how to copy sample data onto the buffer in procesBlock(). In my case, I’m using wav files which are read from memory and into an AudioBuffer object using AudioFormatReader::read(). Thanks!

Here is the code from the tutorial:

void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
const int numInputChannels = fileBuffer.getNumChannels();
const int numOutputChannels = bufferToFill.buffer->getNumChannels();

int outputSamplesRemaining = bufferToFill.numSamples;                                   // [8]
int outputSamplesOffset = bufferToFill.startSample;                                     // [9]

while (outputSamplesRemaining > 0)
{
    int bufferSamplesRemaining = fileBuffer.getNumSamples() - position;                 // [10]
    int samplesThisTime = jmin (outputSamplesRemaining, bufferSamplesRemaining);        // [11]
    
    for (int channel = 0; channel < numOutputChannels; ++channel)
    {
        bufferToFill.buffer->copyFrom (channel,                                         // [12]
                                       outputSamplesOffset,                             //  [12.1]
                                       fileBuffer,                                      //  [12.2]
                                       channel % numInputChannels,                      //  [12.3]
                                       position,                                        //  [12.4]
                                       samplesThisTime);                                //  [12.5]
    }
    
    outputSamplesRemaining -= samplesThisTime;                                          // [13]
    outputSamplesOffset += samplesThisTime;                                             // [14]
    position += samplesThisTime;                                                        // [15]
    
    if (position == fileBuffer.getNumSamples())
        position = 0;                                                                   // [16]
}
}

Hi Naslash,

If I understand the question, it should actually be much simpler! So you’re trying to use this in an AudioProcessor::processBlock() callback?

In this case the start sample will always be 0 and the number of samples will always be the number of samples in the buffer. The AudioSourceChannelInfo struct we use for the AudioSource::getNextAudioBlock() callback makes things a little trickier as it needs to handle sub-blocks.

To try this, you could leave the getNextAudioBlock() function almost as-is and call it from the processBlock() function. You’ll need to remove the override specifier from the getNextAudioBlock() function as you wouldn’t be actually overriding this function:

void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) /*override*/
{
//...

Then in your processBlock() function you can call the getNextAudioBlock() function. You probably need some error checking for places when the fileBuffer object doesn’t hold a valid buffer of data. I assume you’re loading the audio into the buffers before playing starts? If so, then this should work:

void MyAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
    if (fileBuffer.getNumChannels() == 0 || fileBuffer.getNumSamples() == 0) // for example
    {
        buffer.clear();
        return;
    }

    AudioSourceChannelInfo bufferToFill;
    bufferToFill.buffer      = &buffer;
    bufferToFill.startSample = 0;
    bufferToFill.numSamples  = buffer.getNumSamples();
    getNextAudioBlock (bufferToFill);
}
1 Like

Hi martinrobinson,

Thanks for the reply. Yes that is definitely what I was asking, but I don’t quite understand what you did.
So I have an AudioBuffer object with the audio loaded into it through memoryInputStream (binaryData), which is a wav file. So in this case, its the fileBuffer object. If I understand your code correctly, you do some error checking using fileBuffer, but that seems to be where it ends with fileBuffer. Then bufferToFill is created with an empty buffer which is set equal to the processBlock’s &buffer. But in my case, that processBlock() buffer is empty. My audio is in fileBuffer. That’s why I tried using the method in the tutorial to copy the data from fileBuffer into the buffer in processBlock(), which is where I had difficulty as explained in my first post. I tried making bufferToFill.buffer = &fileBuffer in case that’s what you meant, but it still did not work. Since doing that leaves the processBlock buffer untouched I’m not sure what happens to it if output is going through a separate AudioBuffer in processBlock. I’m guessing it’s not supposed to be that way, but I tried it anyway just to be sure. Is there something that I’m missing or misunderstanding? Thanks!

Oh wait, I think I know what the problem might be. I totally misunderstood. I should basically keep the getNextAudioBlock() code from the tutorial, I totally misunderstood that part. When I think about it that way, I now see how your code makes sense. I’ll try that and see how it goes.

hmm it’s still not working properly. There is audio output but it does not sound like the wav file at all. It sounds like a low pitched clicking sound. I’m not sure if it qualifies as clipping. I’ve encountered this issue in my many previous attempts to output audio through processBlock(), before trying this method in the tutorial. There is definitely one improvement though, it gradually fades. In my previous attempts, the sound kept looping.

Here is my code with yours (with some of the code that was already in there by default):

AudioBuffer newBuffer;
AudioFormatManager fm;
int position = 0;

fm.registerBasicFormats(); (this is in the constructor).

void Testing3AudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
const int totalNumInputChannels  = getTotalNumInputChannels();
const int totalNumOutputChannels = getTotalNumOutputChannels();

// In case we have more outputs than inputs, this code clears any output
// channels that didn't contain input data, (because these aren't
// guaranteed to be empty - they may contain garbage).
// This is here to avoid people getting screaming feedback
// when they first compile a plugin, but obviously you don't need to keep
// this code if your algorithm always overwrites all the output channels.
for (int i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
    buffer.clear (i, 0, buffer.getNumSamples());



ScopedPointer<AudioFormatReader> reader (fm.createReaderFor(new MemoryInputStream (BinaryData::C4HARD1_wav, BinaryData::C4HARD1_wavSize, false)));

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



if (newBuffer.getNumChannels() == 0 || newBuffer.getNumSamples() == 0)
{
    buffer.clear();
    return;
}

    AudioSourceChannelInfo bufferToFill;
    bufferToFill.buffer      = &buffer;
    bufferToFill.startSample = 0;
    bufferToFill.numSamples  = buffer.getNumSamples();
    getNextAudioBlock (bufferToFill);

}

This is using the getNextAudioBlock() code from the tutorial as seen on my first post.

I see… not quite like that…

You’d need to preload the file into your newBuffer member. You could do this in the Testing3AudioProcessor constructor or in your Testing3AudioProcessor::prepareToPlay() function.

The clicking you’re hearing is probably audio dropouts as you’re refreshing the entire buffer on each callback (even though this is a memory stream this is probably maxing out the CPU).

1 Like

Oh I see what you mean.
Ok great! I preloaded the file into my newBuffer member in my prepareToPlay() function. This time the sound is clean but still has one issue (almost there though!). It’s playing it in a lower pitch, like an octave or 2 lower. I can’t figure out why.

So I basically have this part in prepareToPlay() now:

ScopedPointer<AudioFormatReader> reader (fm.createReaderFor(new MemoryInputStream (BinaryData::C4HARD1_wav, BinaryData::C4HARD1_wavSize, false)));

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

Thank you for being patient with me, I really appreciate it.

It’s probably simply that the sample rates don’t match. So if your file is say 96kHz and your DAW is at 48kHz then it will sound an octave down. This simple example, and the tutorial, don’t take account of the sample rate.