Reading a .wav file into an array of samples?

I need to read a .wav file into an array of samples. I’ve searched for examples of this, but I’m surprisingly not finding anything current. I’ve read through the documentation for the AudioFormatReader class and I’m in a bit over my head with the use of int** and float**. I get that these are 2d arrays, but I’m still new to this kind of syntax.

calling the read function triggers an error in audioDataConverter on this line:

inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); }

error: Exception thrown: read access violation.
this->data was 0xFFFFFFFFFFFFFFFF.

Here’s my code so far. I was trying out both read and readSamples. They triggered the same error. I also took a guess at how to access these samples in the lines below but haven’t made it there yet due to the error. Any assistance with my syntax would be awesome. Thanks!

if (myChooser.browseForFileToOpen())
{
File audioFile(myChooser.getResult());

        AudioFormatManager formatManager;
        formatManager.registerBasicFormats();
        ScopedPointer<AudioFormatReader> reader = formatManager.createReaderFor(audioFile);
        if (reader != 0)
        {
            uint64 zero = 0;
            int** destChannels;
            float** destBuffer;

            reader->read(destBuffer, 1, zero, (int)reader->lengthInSamples);//<<<<< Error triggered here

            //reader->readSamples(destChannels, 1, 0, 0, (int)reader->lengthInSamples);
            float testSamp1 = **(destBuffer + 1);
            float testSamp2 = **(destBuffer + 10);
            float testSamp3 = **(destBuffer + 100);
        }

    }

I’m also open to any better ways of reading a wav file. I just need the array of samples, don’t really care how I get there.

This is a method I’ve used in the past that takes a file, reads the samples, and returns an audio buffer. I can’t say whether or not this is best practice, but it worked fine for me.

juce::AudioBuffer<float> ClassName::getAudioBufferFromFile(juce::File file)
{
    //juce::AudioFormatManager formatManager - declared in header...`;
    auto* reader = formatManager.createReaderFor(file);
    juce::AudioBuffer<float> audioBuffer;
    audioBuffer.setSize(reader->numChannels, reader->lengthInSamples);
    reader->read(&audioBuffer, 0, reader->lengthInSamples, 0, true, true);
    delete reader;
    return audioBuffer;
}
2 Likes

Awesome! That worked. The AudioBuffer class makes this much more straightforward. Thanks a lot.

Also for anyone else out there who wants to use that code. Make sure to register audio formats.

AudioFormatManager formatManager;
formatManager.registerBasicFormats();

1 Like

The AudioBuffer solution is definitely the better solution, but you maybe want to understand why your approach didn’t work, so let’s have a quick excursion in C++ memory management:

float** is not really a 2D array, it’s just a pointer to a float pointer, so nothing more than a variable intended to point to some 2D array. A real 2D array (float[][]), a 1D array of float pointers (float*[]) and a double float pointer (float**) can be accessed the same, the only difference is that real arrays actually are an existing memory region, while pointers just point to a region managed somewhere else. The way it’s initialized above. However, the read function expects you to pass in a pointer, pointing to an already existing array where it can copy the samples to:

So it’s no real surprise that this crashes. To resolve this one option here would be to set the numbers of sample as a compile time constant size and use a “real” 2D array like this

// channel and sample count specified as compile time constants
constexpr int numChannels = 2;
constexpr int numSamples = 1000;

// A compile time constant sized 2D float array to write the samples to
float destBuffers[numChannels][numSamples];

// This will work, since we know that the array declared above is big enough
reader->read (destBuffers, numChannels, 0, numSamples);

// now you can access the samples like
float testSamp1 = destBuffers[0][1];
float testSamp2 = destBuffers[0][10];
float testSamp3 = destBuffers[0][100];

Now the solution above limits you to know the number of samples needed at compile time, which might not be what you want. Instead you could allocate a matching amount of memory after having figured out, how many samples the reader has for you, like e.g.

// channel count still specified as compile time constant
constexpr int numChannels = 2;

// sample count is read from the reader at runtime
const auto numSamples = reader->lengthInSamples;

// A 1D array of float pointers
float* destBuffers[numChannels];

// Allocate a heap array matching the sample count for each channel and store its address to the pointer array
for (int ch = 0; ch < numChannels; ++ch)
    destBuffers[ch] = new float[numSamples];

// This will now read all the samples available, since we made enough space for them
reader->read (destBuffers, numChannels, 0, numSamples);

// accessing the samples works the same, since float[][], float*[] and float** can be used interchangeably
float testSamp1 = destBuffers[0][1];
float testSamp2 = destBuffers[0][10];
float testSamp3 = destBuffers[0][100];

// IMPORTANT: Allocated memory always has to be deleted after usage to avoid memory leaks
for (int ch = 0; ch < numChannels; ++ch)
    delete[] destBuffers[ch];

What makes this approach a bit ugly is the manual new/delete memory management which should be avoided at all cost in modern C++ – it’s to easy to miss deleting something. Therefore, classes like AudioBuffer that handle all this memory management automatically inside are the best choice here. Still, it is a good advice to know how memory management works in C++ – a lot of bugs are connected to problems arising from there – this is why I wanted you to actually understand what went wrong with your original approach.

3 Likes

That’s great information. So the read function really is just looking for a 2D float array then. I guess I got ‘more’ rather than ‘less’ confused when I started looking at c++ information on **. haha. I’ll keep this in mind when I run into that kind of the stuff in the future. Thanks for the detailed explanation.

while we are at it: one thing i often thought to myself was why audioBuffer has no operator overloading for accessing samples. it’s annoying to always have to call getArrayOfWritePointers to be able to say samples[ch][s] on it or something. i’d like it much more if we could just go audioBuffer(ch, s) directly, because accessing the buffer is the main thing to do with this object so it would make sense to have the ()-operator overloaded like that. this message goes to the JUCE developers, but you just reminded me of this idea i had so it’s a response to your statement. because you were talking about “uglyness” and that extraline of code is something that i dislike almost just as much as having to use new and delete