Filter click artefacts note ON/OFF


We are doing a wavetable synth and we are experimenting one issue with filters.
We get audible plops for an active stateVariableFilter on NoteOn and NoteOff,
however if I turn the Filter off I don’t have clicks, so it does not look like a startSample issue (see my renderNextBlock() ).
It has nothing to do with changing the CutOff Frequency/Filter State too rapidly, it is an issue even for a static CutOff Frequency.

Also changing the filter type produces arfefacts.

Here is a snippet of the code :slightly_smiling_face:

What is going on here?
Is that some sort of awful font that is combining == into a long equals sign?

That loop is just going to iterate once (unless numSamples is zero in which case it never executes). If you meant to loop until numSamples is zero then you want while (--numSamples != 0), otherwise if it’s an assignment it still just iterates once but assigns numSamples to zero.

The --i = 0 in the for loop is also problematic and should similarly be --i != 0, although that would still be wrong because it would only iterate the loop once with i being equal to 1 in the loop body for a stereo configuration (or zero loops for a mono config), however because of the --i = 0 that for loop doesn’t even get to iterate a single time, even if it’s actually a --i == 0 and a weird font, it still won’t iterate (unless in a mono configuration when you get 1 iteration!).

I’m starting to question my own sanity here, is that code wrong as I suggested or am I losing the plot? :joy:

My sanity also misses some closing template brackets, or this is a style I don’t know of?

He’s using a font with ligatures. Which is completely normals and awesome. I use Pragmata Pro Mono with Ligatures from here:

and it is really neat, not only for == but also for >=, -> and other similar constructs.

There are also free alternatives that are very popular:

Except when you post screenshots of code rather than copy/paste and confuse the programmers who don’t use them. I guess it’s a personal choice thing, but imo it’s about as useful as coding with an upside-down font; I could get used to it but it would be a barrier for other people looking at my code :joy:

1 Like

Sorry guys, I don’t know what happened there I am not using a ligature code, but the > was removed, I uploaded the corrected version.
I did a screenshot because I didn’t find a way to display the code in a nice way on the Juce forum :frowning:

use three back ticks on an extra line before and after your code, and it will show up as code:

    int main (int, char**)
        return 0;
1 Like

Thanks, here is the code :

void WavetableVoice::renderNextBlock(AudioBuffer<float>& outputBuffer, int startSample, int numSamples)
	//int temp_startSample = startSample;
	//int temp_numSamples = numSamples;
	AudioBuffer<float> tempAudioBuffer(outputBuffer.getNumChannels(),outputBuffer.getNumSamples());

	while (--numSamples >= 0) // [6]
		auto currentSample = adsr.getNextSample()* waveTableOsc.Process() * level;
		for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
			tempAudioBuffer.setSample(i, startSample, currentSample);

	if (filter1.isActive()) // THIS PRODUCES ARTEFACTS
		dsp::AudioBlock<float> filterBlock(tempAudioBuffer);
		outputBuffer.addFrom(0, 0, tempAudioBuffer, 0, 0, tempAudioBuffer.getNumSamples());
		outputBuffer.addFrom(1, 0, tempAudioBuffer, 1, 0, tempAudioBuffer.getNumSamples());
		//outputBuffer.addFrom(0, temp_startSample, tempAudioBuffer, 0, temp_startSample, temp_numSamples);
		//outputBuffer.addFrom(1, temp_startSample, tempAudioBuffer, 1, temp_startSample, temp_numSamples);
		outputBuffer.addFrom(0, 0, tempAudioBuffer, 0, 0, tempAudioBuffer.getNumSamples());
		outputBuffer.addFrom(1, 0, tempAudioBuffer, 1, 0, tempAudioBuffer.getNumSamples());

Some notes:

It’s not a good idea to allocate memory on a realtime thread especially each time the renderNextBlock is called. It’s better to allocate this once during the prepare call.

Why creating copies of the signal onto several channels, if they all get filtered with the same filter later? Would make more sense to filter only one channel and do the copy part afterwards.

That one is something like a ProcessDuplicator<StateVariableFilter>, right? Otherwise only one channel will be filtered.

What kind of artifacts do you hear? Do they change with the blocksize? Then there’s something going on with the filter’s state (like using one and the same filter for several channels, however that shouldn’t happen with the StateVariableFilter). Have you looked at the waveform of the output? Could help figuring out whats going on.

Hello Daniel,

You are true on many things but these are more optimlisation issues, which are not the cause of the artefacts from what we could test.

Here is the wave curve which show the issue on notes ON/OFF :slight_smile:

The artifacts change with cut-off frequency, as if they were filtered as well, clicking becomes more high-frequent sounding and more direct for low-freq. cut-off… hard to describe in words.FilterNoteOffBlops.cpp (2.9 MB)

I uploaded the wav file, please rename it as .wav instead of .cpp.

About the block size, I have the feeling that if the block size is big (2048) i dont have clicks for high notes, maybe because the wave length is within the block size…but if I hold the note it shouldnt make a difference…

Thanks for your help. :wink:

This image suggests, that adding a delay into the bypassed path would already help a bit.
But that might be only true for that specific measurement you took here.

Looks quite strange, especially as there are sine waves with different frequencies patched together.

I think the culprit is that you write numSamples many samples into your tempAudioBuffer from startPosition, however you send all of the samples (from sample 0) into the filter, so the filter get’s a non-continuous audio signal at the input. And then you write all of them back (you actually only need numSamples many from startSample on.

startSample != 0 might only occur on note-on and note-offs.


Many many thanks that was the issue !:slight_smile::slight_smile: