Using AudioFormatManager and AudioFormatReader to load audio files in a preallocated buffer


#1

I’m trying to use AudioFormatManager/AudioFormatReader to load audio files in a float** preallocated buffer, so something like:

struct Soundfile {
    
    float** fBuffers;
    int fLength;
    int fSampleRate;
    int fChannels;
};

JuceReader(Soundfile* soundfile, const std::string& path_name_str, int max_chan)
{
    soundfile->fBuffers = new float*[max_chan];
    if (!soundfile->fBuffers) {
        throw std::bad_alloc();
   }

   AudioFormatManager formatManager;
   formatManager.registerBasicFormats(); // Currently, this will add WAV and AIFF to the list.
 
  AudioFormatReader* formatReader = formatManager.createReaderFor(File(path_name_str));
 if (formatReader) {
            soundfile->fChannels = int(formatReader->numChannels);
            soundfile->fSampleRate = int(formatReader->sampleRate);
            soundfile->fLength = int(formatReader->lengthInSamples);
            
            for (int chan = 0; chan < soundfile->fChannels; chan++) {
                soundfile->fBuffers[chan] = new float[soundfile->fLength];
                if (!soundfile->fBuffers[chan]) {
                    throw std::bad_alloc();
                }
            }
            
            if (formatReader->read(reinterpret_cast<int *const *>(soundfile->fBuffers), soundfile->fChannels, 0, soundfile->fLength, false)) {
                // Share the same buffers for all other channels so that we have max_chan channels available
                for (int chan = soundfile->fChannels; chan < max_chan; chan++) {
                    soundfile->fBuffers[chan] = soundfile->fBuffers[chan % soundfile->fChannels];
                }
            } else {
                std::cerr << "Error opening the file : " << path_name_str << std::endl;
            }
            
            delete formatReader;
            
        }
}

But the resulting soundfile->fBuffers is filled with 0 and NAN after the (succeeding…) formatReader->read operation. What can be the problem? Thanks.


#2

I would highly recommend to rely on AudioBuffer<float> here for memory management,
a) it follows RAII principles (vs. your new float[x] call can easily leak
b) the methods are much simpler, because they were designed to work together

You still can get the same result, if you simply call afterwards:

float** fBuffers = audioBuffer.getArrayOfWritePointers();

…if you need the raw pointers at all…

Just scrolled down, why are you calling a reinterpret_cast? You better forget, that this exists, for constructions you are using it is definitely the wrong thing. There are very rare cases, where you need a reinterpret_cast. You probably added it in, because it didn’t compile otherwise, so that should have told you, that you are not doing it right.

Please have a look in the Tutorial Looping Audio Sample Buffer

Hope that helps


#3

The use of new float[x] is because this piece on JUCE code is used by some other non-JUCE code that has to use this low-level format.

And about the use of reinterpret_cast ? Well I don’t find the documentation of the read method usingint *const * destSamples, so clear… (see https://docs.juce.com/master/classAudioFormatReader.html#ae90256289d171f649fd83d2070b4ae32).

I will have a look at the AudioBuffer<float> way then.


#4

Understood, that’s why I posted the use of AudioBuffer::getArrayOfWritePointers(), which gives you access to exactly that float** pointer, that your other code uses.

For clarification: AudioSampleBuffer == AudioBuffer<float>.

The read method you used, will return the data keeping the datatype that is used in the file. It is your responsibility to convert the data. But a reinterpret_cast doesn’t convert, it “reinterprets” ignoring the actual type before.
The method using an AudioBuffer converts to float for you.


#5

Well looking at what JUCE read( AudioBuffer<float>....) code does, I just added the following piece of code after formatReader->read:

if (!formatReader->usesFloatingPointData) {
                    for (int chan = 0; chan < soundfile->fChannels; ++chan) {
                        FAUSTFLOAT* buffer = soundfile->fBuffers[chan];
                        FloatVectorOperations::convertFixedToFloat(buffer, reinterpret_cast<const int*>(buffer), 1.0f/0x7fffffff, soundfile->fLength);
                    }
                }

And it just works !, so I guess the way to pass the float** fBuffers; using reinterpret_cast seems to be correct after all…


#6

I can also do:

AudioBuffer<float> audioBuffer(soundfile->fBuffers, soundfile->fChannels, soundfile->fLength);

and use:

formatReader->read(&audioBuffer, 0, soundfile->fLength, 0, false, false);

Without having to convert after reading, which is great. By why in this case the formatReader->read call returns void ?


#7

So the void AudioFormatReader::read (AudioBuffer<float>* buffer...) methods internally uses the bool AudioFormatReader::read (int* const* destSamples...) but without checking the possible false result? Why that? How are we supposed to check for possible errors when reading the file using the void AudioFormatReader::read (AudioBuffer<float>* buffer...) API ?


#8

I can only guess here, since it says in the docs

…and will try to intelligently cope with mismatches between the number of channels in the reader and the buffer.

that this version corrects the errors that would return false in the prior method.

One last word to reinterpret_cast: sure it works, in fact you can use everywhere reinterpret_cast. It is the override for any type checking in C++. But the type checking is usually in your favour. I don’t know, if here a reinterpret_cast is necessary, a static_cast might work as well. Usually if an API is intended for “figure out the type yourself”, it would return a void pointer.
Here are the different cast types explained in casts on cppreference.


#9
  1. static_cast does not work in this case
  2. “try to intelligently cope” : I don’t need too much intelligent JUCE code in this case. I just need to get the audio data, and want to know if it fails. So I’ll keep the lover-level read version and conversion to float afterward.