VST3 Processing Buzz (Debug)

Hi All,

I have been building a music player and VST3 host using vst3sdk. I am able to run and chain multiple plugins on multiple effects channels, identifying input and output buffers.

However, I am having some difficulty debugging a pervasive click/buzz that comes as a result of process()ing the audio through the plugins.

The plugins transform the audio as expected and I am able to control the transformations in real-time as expected through their individual GUI windows. But it creates a horrible click/buzz. Muting the effects channels, the dry channel comes through crystal clear with no artifacts. In this case, all VST3 processing is still occurring, which indicates to me that this is not a CPU performance issue. I am developing on an M1 MacBook Pro with plenty of RAM and with several plugins running I am hitting 30-40% CPU usage. Surely there is room for optimization but shredding my CPU does not seem to be the cause of the click/buzz.

I’m using portaudio as my audio playback mechanism and running with <=256 frames per buffer, the buzz is overpowering. The clicks become more sparse in direct relation to increasing the frames per buffer, and so it seems the click is happening at the end of each process() call. From googling, I have begun to think this is a JACK xrun error but I haven’t found any solution that applies to my situation.

I have looked into setAudioPresentationLatencySamples, which I’m not currently calling, but the FFT curve in the EQ plugin I’m using looks great and in-sync.

I’ve been chasing this one for a few days now so any and all thoughts and/or suggestions are welcome! Let me know what you think!

Full code at: github / nathanielBellamy / groovejr.
Method containing Vst3 process() calls, called from the audio thread:

bool Mixer::mixDown(
  const int audioDataIndex,
  const float* audioDataBuffer,
  const int audioDataSfChannels,
  const int framesPerBuffer
  ) const {
  // called from audio thread
  // do not allocate/free memory!

  for (int c = 0; c < audioDataSfChannels; c++) { // TODO: at the moment, we can assume audioDataSfChannels = 2
    for (int i = 0; i < framesPerBuffer; i++) {
      const auto val = audioDataBuffer[audioDataIndex + 2 * i + c];
      // write dry channel output buffer
      outputBuffer[2 * i + c] = dryChannel.gain * val / channelCount;

      // de-interlace audio into shared effects input buffers
      inputBuffers[c][i] = val;
    }
  }

  for (const auto effectsChannel : effectsChannels) {
    if (channelCount == 1) // dry channel only
      break;

    // process audio plugins
    for (int j = 0; j < effectsChannel->vst3Plugins.size(); j++) {
      // note: buffers have been chained
      const auto currPlugin = effectsChannel->vst3Plugins.at(j);
      currPlugin->audioHost->audioClient->process(currPlugin->audioHost->buffers, framesPerBuffer);
    }

    // write processed audio to outputBuffer
    for (int c = 0; c < audioDataSfChannels; c++) {
      for (int i = 0; i < framesPerBuffer; i++) {
        outputBuffer[2 * i + c] +=
          ( effectsChannel->channel.gain * effectsChannel->vst3Plugins.back()->audioHost->buffers.outputs[c][i] ) / channelCount;
      }
    }
  }

  return true;
}

Thanks and cheers!