1
$\begingroup$


I'm trying to create guitar Wah Wah effect (basically band pass filter with dynamic frequency range). I'm using Steinberg SDK for it. I refactored this code to work in real time and it looks like this:

            double Fc = 2500.0f * Hz + 350.0f; // Hz is a 0-1 float value
            double F1 = 2.0f * sin((PI*Fc) / processSetup.sampleRate);

            yh[0] = inputChannel[0];
            yb[0] = F1 * yh[0];
            yl[0] = F1 * yb[0];

            double Q1 = 2 * 0.05;

            for (int n = 1; n < data.numSamples; n++)
            {
                yh[n] = inputChannel[n] - yl[n-1] - Q1 * yb[n-1];
                yb[n] = (F1 * yh[n] + yb[n - 1]);
                yl[n] = F1 * yb[n] + yl[n - 1];
            }
            for (int n = 0; n < data.numSamples; n++)
                outputChannel[n] = yb[n];

And it basically works but whole proccess added wierd popping sound, listen here.

After analizing FFT I noticed additional low and high frequencies and assume it cause those sounds. Here is example without and with effect:enter image description here But I don't know what is wrong with my code as it is the same like that example in matlab. If someone notice where is the issue I would be grateful for help.
Thank you in advance.


thanks for those reponses. Here are more explenations.
1. To TimWescott, all containers are float.
Here is screen with time domain before effect use and after

enter image description here

  1. To A_A, here whole procces code, but I whink everything before if (data.numSamples > 0) is not important it is only grabbing current controlls values.
tresult PLUGIN_API PlugProcessor::process (Vst::ProcessData& data)
{
    //--- Read inputs parameter changes-----------
    if (data.inputParameterChanges)
    {
        int32 numParamsChanged = data.inputParameterChanges->getParameterCount ();
        for (int32 index = 0; index < numParamsChanged; index++)
        {
            Vst::IParamValueQueue* paramQueue =
                data.inputParameterChanges->getParameterData (index);
            if (paramQueue)
            {
                Vst::ParamValue value;
                int32 sampleOffset;
                int32 numPoints = paramQueue->getPointCount ();
                switch (paramQueue->getParameterId ())
                {
                    case HelloWorldParams::kParamGainId:
                        if (paramQueue->getPoint (numPoints - 1, sampleOffset, value) ==
                            kResultTrue)
                            mParam1 = value;
                        break;
                    case HelloWorldParams::kParamQId:
                        if (paramQueue->getPoint(numPoints - 1, sampleOffset, value) ==
                            kResultTrue)
                            Q = value;
                        break;
                    case HelloWorldParams::kParamHzId:
                        if (paramQueue->getPoint(numPoints - 1, sampleOffset, value) ==
                            kResultTrue)
                            Hz = value;
                        break;
                    case HelloWorldParams::kBypassId:
                        if (paramQueue->getPoint (numPoints - 1, sampleOffset, value) ==
                            kResultTrue)
                            mBypass = (value > 0.5f);
                        break;
                }
            }
        }
    }

    if (data.numSamples > 0)
    {
        uint64 arr;
        getBusArrangement(1, 0, arr);
        int32 numChannels = Vst::SpeakerArr::getChannelCount(arr);


        for (int32 channel = 0; channel < numChannels; channel++)
        {
            float* inputChannel = data.inputs[0].channelBuffers32[channel];
            float* outputChannel = data.outputs[0].channelBuffers32[channel];

            if (this->mBypass)
            {
                for (int32 sample = 0; sample < data.numSamples; sample++)
                {
                    outputChannel[sample] = inputChannel[sample];
                }
                continue;
            }

            if (yl.size() != data.numSamples)
            {
                yl.resize(data.numSamples);
                yh.resize(data.numSamples);
                yb.resize(data.numSamples);
            }

            double Fc = 2500.0f * Hz + 350.0f;
            double F1 = 2.0f * sin((PI*Fc) / processSetup.sampleRate); // it is 44100 Hz

            yh[0] = inputChannel[0];
            yb[0] = F1 * yh[0];
            yl[0] = F1 * yb[0];

            double Q1 = 2 * 0.05;

            for (int n = 1; n < data.numSamples; n++)
            {
                yh[n] = inputChannel[n] - yl[n-1] - Q1 * yb[n-1];
                yb[n] = (F1 * yh[n] + yb[n - 1]);
                yl[n] = F1 * yb[n] + yl[n - 1];
            }
            for (int n = 0; n < data.numSamples; n++)
                outputChannel[n] = yb[n];
        }
    }
    return kResultOk;
} 

And here are settings of Reaper on which I'm running this VST:
enter image description here

Thanks

$\endgroup$
4
  • $\begingroup$ Is this C? Are yh, yb and y1 ints, floats, or what? If they're integers, what's the data width of your machine (16, 32, etc.)? $\endgroup$
    – TimWescott
    Commented Jun 14, 2020 at 23:32
  • $\begingroup$ It may be helpful to capture a pop in the time domain -- making pops from not-pops is a time-domain thing, not a frequency-domain thing. $\endgroup$
    – TimWescott
    Commented Jun 14, 2020 at 23:33
  • 1
    $\begingroup$ The audio sample does not appear to be "public" (?). In any case, would it be possible to post the complete process() of your plugin and the system settings you use to run it? (Buffer size, sampling frequency)? The VST SDK does not take care of "state" between calls, this is up to the plugin itself. $\endgroup$
    – A_A
    Commented Jun 15, 2020 at 7:50
  • $\begingroup$ Thanks for responses. I eddited by question with stuff you said above $\endgroup$
    – unknowed
    Commented Jun 15, 2020 at 13:30

1 Answer 1

-1
$\begingroup$

A couple of guesses

  1. Your bandpass has about 20 dB of gain at the resonance frequency, so it's quite possible that you are clipping something downstream
  2. You do block based processing: that means you need to carry the values of the state variable over from one block to the next. You only initialize on the first block
  3. You will also get artifacts each time when you update the filter coefficients (Q or Fc) unless you mask the state transition somehow

You'd be way better off using a standard biquad implementation. Something like this perhaps https://www.earlevel.com/main/2012/11/26/biquad-c-source-code/

$\endgroup$
2
  • $\begingroup$ Thanks for this answer, I will try example you attached. In case 1, how can I check if it is not clipping? <br> And in case of 2, you mean I should remember e.g. in class member variables which I calculate during proccess one block? If so, which relation should exist between variables from previous block and current one? $\endgroup$
    – unknowed
    Commented Jun 15, 2020 at 14:13
  • $\begingroup$ @unknowed, to check for clipping within the filter sections is hard, but you can check the input and output signal peaks and make an educated guess. Try a gain of -10dB before the filter. If the clicks go away then it hints at clipping. I think it has more to do with the state changes, but checking for clipping is good elimintation. $\endgroup$
    – P2000
    Commented Jul 15, 2020 at 22:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.