Looper buffer plugin bugs

Hey,

For a project for school I decided to make a looper buffer plugin, that will keep a buffer (linearBuffer) that will save the last 2 seconds of audio. I would like to point out that this is my first time ever creating a plugin, so if my code looks unorganized and doesn’t make much sense, you can call it to that (and my own incompetence). Once the plugin is turned on, it starts to read from that buffer instead of the incoming sound. There is an amount knob that will make the looped portion smaller and smaller, which can be used for a buildup in a song for example.

However I am now getting stuck at 2 bugs which I can’t seem to get rid of:

  • If I put the appSettings.amount on 100% (2 seconds), there seems to be a silence at the end of the buffer, even tho before enabling the plugin I have made sure to keep a static sine wave going for more than 2 seconds, which would mean that there are no silences.
  • During looping there are audible ticks when at the end of the loop there is sound aswell as in the beginning of the loop.

Here is my processBlock:

    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

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

    auto bufferSize = buffer.getNumSamples();
    auto linearBufferSize = linearBuffer.getNumSamples();
    AppSettings appSettings = getAppSettings(parameters);

    if (appSettings.isOn) {
        if (!wasOnLastFrame) {
            readPosition = 0;
            wasOnLastFrame = true;
        }
    } else {
        wasOnLastFrame = false;
    }

    auto amount = appSettings.amount;
    if (amount != previousAmount) {
        auto newLoopLength = static_cast<int>(linearBufferSize * (amount / 100) - 1);
        endPosition = newLoopLength;
        previousAmount = amount;
        readPosition = readPosition % endPosition;
    }   

    auto amountOfLoops = 1;
    if (readPosition + bufferSize >= endPosition) {
        amountOfLoops += 1;
    }
    amountOfLoops += std::ceil((bufferSize - (endPosition - readPosition)) / endPosition);
    
    for (int channel = 0; channel < totalNumOutputChannels; ++channel) {
        float* channelData = buffer.getWritePointer(channel);
        if (appSettings.isOn) {
            remaining = bufferSize;
            copyRead = readPosition;
            for (int i = 0; i < amountOfLoops; ++i) {
                auto bufferPos = bufferSize - remaining;
                if (copyRead + remaining >= endPosition) {
                    int numSamplesToEnd = endPosition - copyRead;
                    buffer.copyFrom(channel, bufferPos, linearBuffer.getReadPointer(channel, copyRead), numSamplesToEnd);
                    remaining -= numSamplesToEnd;
                    copyRead = 0;
                } else {
                    buffer.copyFrom(channel, bufferPos, linearBuffer.getReadPointer(channel, copyRead), remaining);
                }
            }

        } else {
            fillBuffer(channel, bufferSize, linearBufferSize, channelData);
        }
    }
    readPosition = (readPosition + bufferSize) % endPosition;

And here is my fillBuffer:

    int delta = 1;
    if (isFull) {
        for (int i =0 ; i < linearBufferSize - bufferSize; ++i) {
            linearBuffer.setSample(channel, i, linearBuffer.getSample(channel, i + bufferSize));
        }
        linearBuffer.copyFrom(channel, writePosition - bufferSize - delta, channelData, bufferSize);
    } else {
        int ans = bufferSize;
        if (bufferSize + writePosition >= (linearBufferSize - delta)) {
            int numToShift = (bufferSize + writePosition) - (linearBufferSize - delta);
            for (int i = 0; i < (writePosition - numToShift); ++i) {
                int sample = linearBuffer.getSample(channel, i + numToShift);
                linearBuffer.setSample(channel, i, sample);
            }
            writePosition -= numToShift;
            isFull = true;
        }
        linearBuffer.copyFrom(channel, writePosition, channelData, bufferSize);
        writePosition += ans;
    }

I hope someone is able to help me seeing where I am wrong! Thank you in advance.

I think I have understood your main idea:

  • when appSettings.isOn=true, you loop samples from the buffer (and you can also adjust the loop length)
  • when appSettings.isOn=false, you push samples into the buffer (as a FIFO)

There are too many variables in your code, which can easily introduce bugs. To be honest, the code is too complex to review :cold_face: Would you like to make use of juce::AbstractFifo or boost::circular_buffer?

that is exactly it! I will check out these functions! thanks for your time