Load impulse response from existing audio buffer for dsp::Convolution

Hi,
I’m working on dsp::Convolution and I want to use a pre-defined vector of float for testing. All codes are inside the prepareToPlay function.

convBuffer.setSize(1, 300);
/* assign values to convBuffer... */

conv.reset();
conv.prepare(spec);
conv.loadImpulseResponse(
        &convBuffer,
        spec.sampleRate,
        dsp::Convolution::Stereo::yes,
        dsp::Convolution::Trim::no,
        0,
        dsp::Convolution::Normalise::no);

However, I got the jassert error.

void ResamplingAudioSource::setResamplingRatio (const double             
samplesInPerOutputSample)
{
    jassert (samplesInPerOutputSample > 0); // comes from here...

    const SpinLock::ScopedLockType sl (ratioLock);
    ratio = jmax (0.0, samplesInPerOutputSample);
}

I also read the doc.
https://docs.juce.com/master/classdsp_1_1Convolution.html#add9aaabf3c0211a00f7533dd3951069f
But still have no idea how to use it…

I think I made the wrong function call. But how to fix it?
Thank you!

I think you may be accidentally calling the overload of loadImpulseResponse which takes a const void* parameter instead of an AudioBuffer<float>&&.

You can call the correct overload by moving the buffer:

AudioBuffer<float> convBuffer;
convBuffer.setSize (1, 300);

conv.loadImpulseResponse (std::move (convBuffer),
                          spec.sampleRate,
                          dsp::Convolution::Stereo::yes,
                          dsp::Convolution::Trim::no,
                          0,
                          dsp::Convolution::Normalise::no);
conv.prepare (spec);
1 Like

It works!

Thank you.

I hit this problem as well. Why is there an overload of loadImpulseResponse taking a const void* when that leads to a zero originalSampleRate and a crash? How is it supposed to be used properly?

This parameter should receive the raw bytes of an audio file. You can get these bytes by adding an audio file to the project’s BinaryData, or by using a FileInputStream to read an audio file into a MemoryBlock.

OK, but in that case how do we set the originalSampleRate?

The loadImpulseResponse overload that takes in the void* buffer figures out the original IR’s sample rate from the audio file data that should be in the buffer. There is no parameter for the sample rate in that overload.

If you just want to use floating point audio that is already somehow in memory with a known sample rate, there is the overload that takes in a juce::AudioBuffer and lets you specify the sample rate of the data.

According to the API documentation (partially copied below), this overload of loadImpulseResponse does not have access to the original file and therefore no way to determine the sampling rate of the binary data. It also documents that resampling is supported as needed. It therefore appears there is a missing required sourceDataSampleRate argument in this constructor, unless there is some other way to set originalSampleRate that I haven’t seen. One way to save this constructor would be to bypass sampling-rate conversion service when originalSampleRate is found to be 0 (instead of simply crashing), but I would introduce the new required argument.

Incidentally, I was introduced to this constructor by ChatGPT-4, so it’s “in the weights” and will likely be a ongoing pothole for others down the road. Unless I (and GPT-4) somehow missed the proper setup and usage, it should really be fixed one way or the other.

void dsp::Convolution::loadImpulseResponse	(const void * sourceData, size_t sourceDataSize, ... )		

This function loads an impulse response audio file from memory, added in a JUCE project with the Projucer as binary data.

It can load any of the audio formats registered in JUCE, and performs some resampling and pre-processing as well if needed.

The “binary data” is supposed to be the exact same data as the original audio file, not already parsed/decoded raw audio data, so it will have all the needed metadata like the sample rate available. You can’t use it with raw audio files, the data needs to be contained in something like a WAV or Flac file.

Ok, so it sounds like you’re saying the user of the audio binary data is responsible for parsing the soundfile header in the BinaryData written by Projucer. In this case that would be this overload of loadImpulseResponse, right? It should set originalSampleRate from the header in the binary data. I see a convenient originalFilenames[] array in BinaryData.cpp, so it can use that to determine which header format to find. Is this the bug?

If you are loading an audio file (.wav, .aiff etc.) into the convolution, then that file should already “know” its sample rate, bit depth, and so on. You can pass the entire contents of the file directly into loadImpulseResponse, and the Convolution will resample the impulse response if necessary (i.e. if the sampling rate passed to prepare differs from the sampling rate of the audio data). However, if you’re loading audio from a file, there’s a more convenient overload that takes a juce::File directly. In both of those cases, the Convolution will work out the sampling rate of the audio data automatically. There’s no way (and no need) to pass in a custom impulse response sampling rate.

If you need access to the original sampling rate for some other purpose, then it might make more sense to use the overload of loadImpulseResponse that accepts an AudioBuffer<float> and a double sampleRate. You can use AudioFormatManager and AudioFormatReader in order to query the sample rate of the file and read it into a buffer.

Ok thanks for your recommendations. Maybe there is something wrong with my .wav file. It plays fine at the command line using sox, and sndfile-info looks good, but it crashes loadImpulseResponse. I believe it was created by Octave (from acoustic measurements). I’m already using the constructor you recommend where I just tell it originalSampleRate, but I wanted to get to the bottom of the crash, since it shouldn’t happen, but maybe later during the summer!

If there’s a crash, that suggests that there’s a bug somewhere in the WAV file loader. Even if JUCE can’t load the file successfully, it should fail gracefully rather than crashing.

If you’re able to share the problematic file, that would be very helpful for tracking down the problem. If you’d prefer not to share it publicly, you should be able to send it in a direct message to one of us on the JUCE team, or put it in an email to info (at) juce (dot) com.

Ok, thanks for taking a look. I made a simple example named ConvCrash.
I tried it on a different .wav file and it crashes similarly.
When it works, a gong plays once per second or so.
To make it work, change
#define AVOID_THE_CRASH 0
to
#define AVOID_THE_CRASH 1
I’m simply running with the working case for now.

juce::dsp::Convolution::loadImpulseResponse has three overloads:

void loadImpulseResponse (const void* sourceData, size_t sourceDataSize, ...
void loadImpulseResponse (const File& fileImpulseResponse, Stereo isStereo, ...
void loadImpulseResponse (AudioBuffer<float>&& buffer, double bufferSampleRate, ...

The following code is selecting the first overload. However, as explained in the documentation for this overload, the first two arguments are expected to be a data block, and the size of the block in bytes.

If I replace the first two arguments with BinaryData::oneGong_wav, BinaryData::oneGong_wavSize, then the convolution appears to load the file correctly.

#else // Failing API because originalSampleRate is zero:
    convolutionEngine.loadImpulseResponse (&impulseResponse, // std::move (impulseResponse),
                              sampleRate,
                              stereo,
                              dsp::Convolution::Trim::no,
                              0,
                              dsp::Convolution::Normalise::no);
1 Like

Ok great, thanks. So the first overload does not expect the data block to contain a .wav file with a header. My question then becomes how do we use the first overload correctly? I imagine there is some way to set the original sampling rate separately before calling this function. If not, then my suggestion is to make the original sampling rate a required argument:

void loadImpulseResponse (const void* sourceFloatData, 
                          size_t sourceFloatDataLength,
                          double sourceDataSampleRate, ...

I would also add Float adjectives as above, in the absence of templating, or introduce a new data-type enum, or the like, to avoid confusion about that. (I think 16-bit fixed-point is most commonly used.)

Apologies if this overload turns out to make sense as is in some usage.

It sounds like there’s some confusion about how BinaryData works. When a file is added to the BinaryData, it is converted, byte-for-byte, into an array of bytes that gets compiled into the final program. No modification is made to the content of the file at this point. Every byte from the original file ends up in the BinaryData. This includes the bytes at the beginning of the .wav that specify the sampling rate.

When we pass BinaryData::oneGong_wav, BinaryData::oneGong_wavSize as the first two arguments to the first overload of loadImpulseResponse, we are passing a data block that contains a .wav file with a header.

The intended use-case for this overload is for loading samples from BinaryData. The first argument is a pointer to an array of bytes generated in BinaryData, and the second argument is the number of bytes in that array.

Ok, I think that’s enough about this. Everyone seems to understand everything the way they want to understand it, and anything I would say now would repeat from above, so I’m happy to end it here. Thanks to all responders! I’m sure we all have bigger fish to fry :slight_smile: .

Dear reuk!
I have the same problem, but the solution which you gave, doesnt working, because the function doesnt accept the
std::move (convBuffer)
as the parameter of the loadImpulseResponse function.
May you have any suggestion for me?

EDITED: I have found the solution, I was soo blind. If someone have similar issue, how to call the loadImpulseResponse function, the ovverrides have soo similar parameters, they fooled me