Load binary .wav files into AudioSampleBuffer array

Hi all,

Sorry for nooby question, I’m relatively new to JUCE.

I basically want to load a long list of very short .wav files into an array that I can access in my code. At the moment I am just trying to add one file to the AudioSampleBuffer and I’m having some trouble with that. Here’s my constructor for the AudioProcessor() class:

SoundFile = File(BinaryData::impulse_wav);
AudioFormatManager manager;
manager.registerBasicFormats();
Reader = manager.createReaderFor(SoundFile);
Buffer.setSize(Reader->numChannels, Reader->lengthInSamples);
Reader->read(&Buffer, 0, Reader->lengthInSamples, 0, true, true);

I declared the AudioFormatManager’s pure virtual method ‘readSamples’ and these variables in the AudioProcess class header:

AudioSampleBuffer Buffer;
AudioFormatReader Reader;
File SoundFile;

Inside processBlock, here is my attempt to iterate through the channels and samples in the AudioSampleBuffer:

for (int channel = 0; channel < totalNumInputChannels; ++channel)
{
    auto* channelData = buffer.getWritePointer (channel);
    buffer.addFrom(channel, 0, Buffer, channel, 0, buffer.getNumSamples(), 1);
    for (int i = 0; i < bufferSize; ++i)
    {
        buffer.getWritePointer(channel)[i];
    }
}
  1. Does anything stand out to anyone as being wrong?

  2. I’m getting the error ‘Constructor for ‘PlaygroundAudioProcessor’ must explicitly initialize the base class ‘juce::AudioFormatReader’ which does not have a default constructor’. I know this is because I am not initialising the AudioFormatReader class properly but I’m not sure how to initialise it in the constructor, would I do so in the AudioProcessor constructor like this?

    readSamples(0,0,0,0,0);
    
  3. When I do eventually get this working, I hope to be able to fill an array of AudioSampleBuffers from a list of .wav binaries I will load into the Projucer, I’m presuming the best way to do this would be to run through the initialising process in the AudioProcess constructor with a for() loop, but can anyone think of a more efficient way of doing this (I will be loading 1000s of extremely short .wav file impulse responses!)

Thank you! Sorry for the long post but I am quite lost…

No need to use * in auto since it already becomes a pointer if the function you call returns a pointer (which indeeds does). That’s not a problem tho, AFAIK it will swallow it

For the rest I’d suggest you to take a look at this: it has everything you need, it also loads it in another thread which is cool so you don’t have to stop your audio (if you don’t need it just look at this). If you don’t need the filechooser window just hardcode the path to the file in a string and pass it.

Thanks you @johngalt91!

I’ve managed to create a reader for the AudioFormatReader, but I can’t get audio out after I’ve loaded this data into an AudioBuffer and attempted to iterate through it in processBlock - here’s my code, do you have 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;
    }
}

Just a tip: Usually you first iterate over number of channels, and inside that (for each channel) over the buffer. In your case you are jumping over channels for each sample giving a much worse memory read performance (sequential reads are faster and produce less cache misses).

About what’s wrong, I usually don’t use that aproach so I can’t tell if something’s missing. What I usually do in processblock, for simplicity, is using buffer.getWritePointer(channel) in the channel loop, and then just use it to write to your buffer. To get what I mean:


for (int ch = 0; ch < numberofChannels; ch++)
{
    auto buffer_ptr = buffer.getWritePointer(ch);
    for (int i = 0; i < bufferSize; i++)
    {
        buffer_ptr[i] = file_ptr[i]; // where file_ptr basically is another pointer to your file's respective channel
    }
}

This part is pretty straightforward so usually it’s about how are you reading the file/having a pointer to access it. Just DBG or std::cout the file_ptr indexes in the loop (i.e file_ptr[i]) as a test to see if you are actually pointing to your file. Or put breakpoints to check all the variables values to get a grasp on what’s going on.

You should also be aware on how you increase the position counter, but since you just advance as many samples as the buffer has in each block, it’s just adding that amount

Thank you so much @johngalt91!

I managed to get it working by creating an AudioFormatReaderSource object with the WavAudioFormat reader objects as one of the parameters, and calling its prepareToPlay method in the my audio processor’s prepareToPlay function, and then calling the AudioFormatReaderSource’s getNextAudioBlock in my audio processor’s processBlock function.

However I have run into another issue that regards filling AudioSampleBuffers with my .wav files. I am attempting to do so in a class I have created called IRBank. I have declared these in its header:

AudioFormatReader* reader;
int streamNumChannels;
int streamNumSamples;

HeapBlock<AudioSampleBuffer> bufferArray[3];

And implemented them like so:

for (int i = 1; i < BinaryData::namedResourceListSize; ++i)
{
    const char* binaryData = 0;
    int binaryDataSize = 0;
    
    binaryData = BinaryData::getNamedResource(BinaryData::namedResourceList[i], binaryDataSize);
    
    auto* inputStream = new MemoryInputStream (binaryData, binaryDataSize, false);
    WavAudioFormat format;
    reader = format.createReaderFor (inputStream, true);

    // If reader was successfully created, load into AudioSampleBuffer array
    if (reader)
    {
        int streamNumChannels = reader->numChannels;
        int streamNumSamples = (int)reader->lengthInSamples;
        
        bufferArray[i].allocate(binaryDataSize, true);
        *bufferArray[i] = juce::AudioSampleBuffer(streamNumChannels, streamNumSamples);
        reader->read(bufferArray[i], 0, streamNumSamples, 0, true, true);
    }
}

My main concern is that I’m not using the HeapBlock pointer correctly, as I get this error in the message in the JUCE thread in the HeapBlock::allocate:

JUCE Message Thread (1): signal SIGABRT

I’ve tried putting a delete[ ] operator in the IRBank destructor but still no dice, do you have any ideas? By the way it’s worth mentioning that I start from i = 1 in the for() loop as I have loaded a .xconfig file into the first binary list slot.

That is technically correct, but I learned it that way to prefer making the pointer explicit.
The reason is, that if a non pointer is assigned to that variable, the compiler can flag that.

See also:

3 Likes