How to create a delay buffer and loop it?

Hi,
I am trying to follow the AudioProgrammer’s tutorials to create a delay effect.The only difference is that I don’t want to use it as a delay effect. It will be something like a freeze effect. So I don’t use many things actually. All I need is a fully functioning delay buffer. It should always store the last 2 seconds of my input signal. When I press a button to activate the freeze feature, it should cut the input and loop that 2 seconds buffer. I tried to use the same method that is being shown in these videos form part one to three

https://www.youtube.com/watch?v=IRFUYGkMV8w

But when I try to compile it and test it in reaper, I get a bad memory access error.

 0x1003b78bf <+1391>: cvtss2sd (%rdi), %xmm0

 reaper/mediaafx (15): EXC_BAD_ACCESS (code=1, address=0x0)

COULD YOU PLEASE HAVE A LOOK AT MY CODE AND HELP ME TO SOLVE THIS ISSUE ?

processor.h

AudioBuffer<float> mFreezeBuffer;

int    mWritePos        = 0;
int    mSampleRate      = 44100;
int    mFreezeTime      = 2000;
   

   
void fillFreezeBuffer (int channel,
                       const int bufferLenght,
                       const int freezeBufferLenght,
                       const float* bufferData,
                       const float* freezeBufferData);
void getFromFreezeBuffer (AudioBuffer<float>& buffer,
                          int channel, const int bufferLenght,
                          const int freezeBufferLenght,
                          const float* bufferData,
                          const float* freezeBufferData);

processor.cpp

void FreezeAudioProcessor::prepareToPlay ( double sampleRate, int samplesPerBlock)

{
 mFreezeTime = 2000;  //just for testing
 mSampleRate = sampleRate;

 // sample buffer for 2 second + 2 buffers safety
 mFreezeBuffer.setSize (getTotalNumOutputChannels(),
                        2 * (samplesPerBlock + sampleRate));
 mFreezeBuffer.clear();


}



  void FreezeAudioProcessor::processBlock (AudioBuffer< float >& buffer, MidiBuffer& midiMessages)

{

 ScopedNoDenormals noDenormals;

 const int totalNumInputChannels = getTotalNumInputChannels();

 const int totalNumOutputChannels = getTotalNumOutputChannels();



 for ( int i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
    buffer.clear (i, 0, buffer.getNumSamples());

for (int channel = 0; channel<totalNumInputChannels; ++channel)
{
    const float* bufferData = buffer.getReadPointer (channel);
    const float* freezeBufferData = mFreezeBuffer.getReadPointer (channel);
    
       // copy the data from main buffer
    fillFreezeBuffer (channel,
                      bufferLength,
                      freezeBufferLength,
                      bufferData,
                      freezeBufferData);
      // fill it back with freezebuffer
       // when i uncomment getFromFreezeBuffer i get a bad access memory error.
    getFromFreezeBuffer (buffer,
                         channel,
                         bufferLength,
                         freezeBufferLength,
                         bufferData,
                         freezeBufferData);

    mWritePos += bufferLength;// to move to next 512 samples
    mWritePos %= freezeBufferLength ;// to go back to the beginning
     }


  }

 void FreezeAudioProcessor::fillFreezeBuffer (int channel,
                                              const int bufferLength,
                                              const int freezeBufferLenght,
                                              const float* bufferData,
                                              const float* freezeBufferData)
{
       // if the freeze buffer still can store another 512 samples in it
if (freezeBufferLenght > bufferLength + mWritePos)
{
    mFreezeBuffer.copyFrom (channel,
                            mWritePos,
                            bufferData,
                            bufferLength,
                            0.8f);
   
}
else // if the freeze buffer cannot store another 512 samples, fill the remaining samples of the freeze buffer and go back to first index of  freeze buffer
{
    const int bufferRemaining = freezeBufferLenght - mWritePos;
    mFreezeBuffer.copyFrom (channel,
                                   mWritePos,
                                   bufferData,
                                   bufferRemaining,
                                   0.8f);
    mFreezeBuffer.copyFrom (channel,
                            0,
                            bufferData/*+ bufferRemaining*/,
                            bufferLength - bufferRemaining,
                            0.8f);
}
}


 void FreezeAudioProcessor::getFromFreezeBuffer (AudioBuffer<float>& buffer,
                                                 int channel,
                                                 const int bufferLength,
                                                 const int freezeBufferLength,
                                                 const float* bufferData,
                                                 const float* freezeBufferData)
 {
         const int readPos = static_cast<int> (freezeBufferLength + mWritePos - (mSampleRate * mFreezeTime / 1000) % freezeBufferLength);

if (freezeBufferLength > bufferLength + readPos)
{
    buffer.copyFrom (channel, 0, freezeBufferData + readPos, bufferLength);
}
else
{

    const int bufferRemaining = freezeBufferLength - readPos;
    buffer.copyFrom (channel, 0, freezeBufferData + readPos, bufferRemaining);
    buffer.copyFrom (channel, bufferRemaining, freezeBufferData, bufferLength - bufferRemaining);
}
}

The reason that this is crashing is because processBlock() will begin calling repeatedly as soon as your plug-in is created. The problem is that your plug-in may not have called prepareToPlay() yet to give mFreezeBuffer it’s size and settings, and that’s exactly what it’s telling you here. EXC_BAD_ACCESS essentially means “I’m expecting something to be here and it’s not.”

The way I handle this to create a member variable in your header bool mIsInit { false };

Then in prepareToPlay() at the end you would change that to true to show your plug-in is now at a state where it’s ready to start processing audio mIsInit = true;

Then in the beginning of processBlock() you indicate that if prepareToPlay() has not been called, you want to return out of processBlock() which will skip over the rest of the code until mIsInit is true…

void FreezeAudioProcessor::processBlock (AudioBuffer< float >& buffer, MidiBuffer& midiMessages)

{
    if (! mIsInit)
        return;

    // ... The rest of your audio processing code
 }

By the way, the new DSP delay line in JUCE 6 is excellent for this. I’m working on a video tutorial for it, but in the meantime you can check out the DSP examples in the examples folder in the JUCE application folder (examples/plugins/DSPModulePluginDemo.h).

Sorry to object. It is guaranteed that prepareToPlay has finished before processBlock() can be called. There is no check, but it is guaranteed by contract. That means a host that calls processBlock() before prepareToPlay() would be considered broken.

There is probably something else going on, e.g.

if (freezeBufferLenght > bufferLength + mWritePos)

should be >=
also if totalNumInputChannels > totalNumOutputChannels, it would crash as well…

1 Like

First of all THANK YOU VERY MUCH !

It is great to get an answer from you :slight_smile: After adding the mIsInit now I don’t get the same error but still i have a bad memory access issue :slight_smile: And also I will be looking forward to see that video :slight_smile:

This is what I get. Looks like it is because of my copyFrom() method.

jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size);

but it looks fine to me :slight_smile:

I changed your code from this

   if (! mIsInit)
         return;

to this

if (! mIsInit)
{
    
    DBG(" mIsInit is false!");
     return;
}
else
{
    
     DBG(" mIsInit is true now!");
}

I get “mIsInit is true now!” as output. So gets into the prepareToPlay and it changes the mIsInit from “false” to “true”.

the jassert stops execution to let you inspect the values, that didn’t meet the expectation. That should allow you to spot the error.

To spot the exact point, in my getFromFreezeBuffer

  void FreezeAudioProcessor::getFromFreezeBuffer (AudioBuffer<float>& buffer,
                                                 int channel,
                                                 const int bufferLength,
                                                 const int freezeBufferLength,
                                                 const float* bufferData,
                                                 const float* freezeBufferData)
  {
const int readPos = static_cast<int> (freezeBufferLength + mWritePos - (mSampleRate * mFreezeTime / 1000) % freezeBufferLength);

if (freezeBufferLength > bufferLength + readPos)
{
    DBG(" first line");
    buffer.copyFrom (channel, 0, freezeBufferData + readPos, bufferLength);
}
else
{
    DBG(" second line");
    const int bufferRemaining = freezeBufferLength - readPos;
    buffer.copyFrom (channel, 0, freezeBufferData + readPos, bufferRemaining);
    DBG(" second line2");
    buffer.copyFrom (channel, bufferRemaining, freezeBufferData, bufferLength - bufferRemaining);
     DBG(" second line3");
}
}

mIsInit is true now!

first linee

first line

mIsInit is true now!

first line

first line

mIsInit is true now!

first line

first line

mIsInit is true now!

first line

first line

mIsInit is true now!

first line

second line

second line2

second line3

mIsInit is true now!

second line

JUCE Assertion failure in juce_AudioSampleBuffer.h:881

(lldb)

Looks like it reads the buffer till the end but at the end it does not go back to the beginning . It crashes somewhere in the tail.

Thank you :pray:

Looks like it is exceeding the boundary

@JoshuaHodge @Daniel Thank you very much :slight_smile: I found the problem. As I mentioned it was about the getfromFreeze. In getfromFreeze, mWritePos values were not correct. My mWritePos was in wrong place in the processBlock. After changing that it is fine now.
This problem aside, it was great to get answer from both of you :slight_smile:
Big thanks to you :pray: :slight_smile:

1 Like

You’re welcome :slight_smile: