Crackling Audio when recieving Aftertouch or CC

Hey,
I am currently using JUCE for the first time and my plan is to implement a small, standalone subtractive Synth.
The structure is as follows:

  • the MainComponent inherits AudioAppComponent and ChangeListener
  • the MainComponent containts a Synthesiser, a MidiMessageCollector and an AudioSetupComponent
  • I implemented a SynthesiserVoice which consist of a ProcessorChain<Oscillator, Gain> that I added to the Synthesiser in the MainComponent
  • I use the AudioDeviceSelectorComponent to change the MidiDevice, and the ChangeListenerCallback to register the MidiCallback of the selected devices

What works: selecting Midi devices, recieving Midi notes and passing them through a ProcessContextReplacing to the processorChain. The Synth plays the notes and stops when I let go.

Now the Issue: As soon as I start generating CCs or Aftertouch my Audio starts crackling really bad.
Weirdly the controllerMoved method of my SynthVoice never gets called.

SytnthVoice Implentation:

SynthVoice::SynthVoice() {
    auto &osc = processorChain.get<oscillatorIndex>();
    osc.initialise([](float x) { return x / juce::MathConstants<float>::pi; },
                   100);
    osc.setFrequency(440.0f);
    auto &gain = processorChain.get<gainIndex>();
    gain.setGainLinear(0.0f);
}

bool SynthVoice::canPlaySound(juce::SynthesiserSound *sound) {
    return dynamic_cast<SynthSound *>(sound) != 0;
}

void SynthVoice::startNote(int midiNoteNumber, float velocity,
                           juce::SynthesiserSound *sound,
                           int currentPitchWheelPosition) {
    noteNumber = midiNoteNumber;
    frequency = noteHz(midiNoteNumber, 0);
    level = velocity;
    auto &osc = processorChain.get<oscillatorIndex>();
    osc.setFrequency(frequency);
    auto &gain = processorChain.get<gainIndex>();
    gain.setGainLinear(0.125f);
}

void SynthVoice::stopNote(float velocity, bool allowTailOff) {
    clearCurrentNote();
    auto &gain = processorChain.get<gainIndex>();
    gain.setGainLinear(0.0f);
}
void SynthVoice::pitchWheelMoved(int newPitchWheelValue) {}
void SynthVoice::controllerMoved(int controllerNumber, int newControllerValue) {
}
void SynthVoice::prepare(const juce::dsp::ProcessSpec &spec) {
    processorChain.prepare(spec);
}
void SynthVoice::renderNextBlock(juce::AudioBuffer<float> &outputBuffer,
                                 int startSample, int numSamples) {
    juce::dsp::AudioBlock<float> block(outputBuffer, startSample);
    juce::dsp::ProcessContextReplacing<float> context(block);
    processorChain.process(context);
}

MainComponent Implementation:

MainComponent::MainComponent()
    : audioSetupComponent(deviceManager, 0, 0, 0, 2, true, true, true, true) {
    setSize(800, 600);
    synth.clearVoices();
    synth.clearSounds();
    synth.addVoice(new SynthVoice);
    synth.addSound(new SynthSound);
    deviceManager.addChangeListener(this);

    if (juce::RuntimePermissions::isRequired(
            juce::RuntimePermissions::recordAudio) &&
        !juce::RuntimePermissions::isGranted(
            juce::RuntimePermissions::recordAudio)) {
        juce::RuntimePermissions::request(
            juce::RuntimePermissions::recordAudio,
            [&](bool granted) { setAudioChannels(0, 2); });
    } else {
        setAudioChannels(0, 2);
    }

    addAndMakeVisible(audioSetupComponent);
}

MainComponent::~MainComponent() { shutdownAudio(); }

//==============================================================================
void MainComponent::prepareToPlay(int samplesPerBlockExpected,
                                  double sampleRate) {
    synth.setCurrentPlaybackSampleRate(sampleRate);
    auto voice = dynamic_cast<SynthVoice *>(synth.getVoice(0));
    voice->prepare({sampleRate, (juce::uint32)samplesPerBlockExpected, 2});
    midiCollector.reset(sampleRate);
}

void MainComponent::getNextAudioBlock(
    const juce::AudioSourceChannelInfo &bufferToFill) {
    bufferToFill.clearActiveBufferRegion();
    int numSamples = bufferToFill.numSamples;
    int startSample = bufferToFill.startSample;
    juce::MidiBuffer midiBuffer;
    midiCollector.removeNextBlockOfMessages(midiBuffer, numSamples);
    synth.renderNextBlock(*bufferToFill.buffer, midiBuffer, startSample,
                          numSamples);
}

void MainComponent::releaseResources() {}

//==============================================================================
void MainComponent::paint(juce::Graphics &g) {
    g.fillAll(
        getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
}

void MainComponent::resized() {
    audioSetupComponent.setBounds(0, 50, getWidth() - 210, 20);
}

void MainComponent::changeListenerCallback(juce::ChangeBroadcaster *source) {
    auto midiDevices = juce::MidiInput::getAvailableDevices();

    for (auto dev : midiDevices) {
        if (deviceManager.isMidiInputDeviceEnabled(dev.identifier)) {
            deviceManager.addMidiInputDeviceCallback(dev.identifier,
                                                     &midiCollector);
        } else {
            deviceManager.removeMidiInputDeviceCallback(dev.identifier,
                                                        &midiCollector);
        }
    }
}

}

I am using Linux (tried ALSA, and Jack) and the Issue seems to be indipendent of samplerate and buffersize. I can send more of my source code if it helps, but not much is happening there.
Thanks in advance for any guidance

If your audio starts crackling and making noises it can either be high CPU usage (doing too much stuff, if you are allocating memory, or using system i/o calls, or are doing something bizarre that shouldn’t be doing in the audio processing), or buffer problems (mismatching number of samples, i.e starting with startSample but not processing from startSample to startSample + numSamples, only doing to numSamples), etc. The easiest thing you can do is profile your code to see if there’s something wrong going on, and debug/print a simple sine wave and see if you have jumps in your samples

What solved the problem for now is calling
synth.setMinimumRenderingSubdivisionSize(samplesPerBlockExpected, true) in MainComponent::prepareToPlay() the crackling is gone for now.