3

Here is latest version that produce effect close to the desired

void DeleteFrequencies(short *audioDataBuffer, const int bufferSize, int lowestFrequency, int highestFrequency, int sampleRate )
{
    int frequencyInHzPerSample = sampleRate / bufferSize;
    /*             __________________________
    /* ___________                           __________________________  filter kernel   */
    int nOfPointsInFilterKernel = (lowestFrequency / frequencyInHzPerSample) + ( bufferSize - highestFrequency / frequencyInHzPerSample);
    U u;
    double *RealX = new  double[bufferSize];
    double *ImmX = new  double[bufferSize];
    ShortArrayToDoubleArray(audioDataBuffer, RealX, bufferSize);

    // padd with zeroes, so that inputSignalSamplesNumber + kernelLength - 1 = bufferSize

    // convert to frequency domain
    ForwardRealFFT(RealX, ImmX, bufferSize);
    // cut frequences < 300 && > 3400
    int Multiplyer = 1;
    for (int i = 0; i < 512; ++i)
    {
        if (i * 8000 / 1024 > 3400 || i * 8000 / bufferSize < 300 )
        {
            RealX[i] = 0;
            ImmX[i] = 0;
        }
        if (i < lowestFrequency / frequencyInHzPerSample || i > highestFrequency / frequencyInHzPerSample )
            Multiplyer = 0;
        else 
            Multiplyer = 1;
        RealX[i] = RealX[i] * Multiplyer /*ReH[f]*/  - ImmX[i] * Multiplyer;
        ImmX[i] = ImmX[i] * Multiplyer + RealX[i] * Multiplyer;

    }
    ReverseRealFFT(RealX, ImmX, bufferSize);
    DoubleArrayToShortArray(RealX, audioDataBuffer, bufferSize);
    delete [] RealX;
    delete [] ImmX;
}

enter image description here

but why it works this way???

Important that I just started learning DSP, so I can be unaware of some important ideas (i appologise for that, but I have task which I need to solve: i need to reduce background noise in the recorder speeach, I try to approach that by cuting off from recorded speech frequencies in ranges <300 && > 3700 (as human voice in [300;3700] range) I started from that method as it is simple, but I found out - it can`t be applied (please see - https://dsp.stackexchange.com/questions/6220/why-is-it-a-bad-idea-to-filter-by-zeroing-out-fft-bins/6224#6224 - thanks to @SleuthEye for reference).
So can you please suggest me simple solution based on the FFT usage that will allow me at least remove given ranges of frequneces?

I am trying to implement ideal band pass filter. But it isn't working as I expect - only high frequencies are cut.

Here is my implementation description:

  1. Read ampiltude values from PCM (raw) 16 bit format with sampling rate 8000 hz to the buffer of shorts of size 1024
  2. Apply FFT to go from time domain to the frequency domain
  3. Zero all frequencies < 300 and > 3700:
  4. Inverse FFT

union U
{
    char ch[2];
    short sh;
};
std::fstream in;
std::fstream out;
short audioDataBuffer[1024];
in.open ("mySound.pcm", std::ios::in | std::ios::binary);
out.open("mySoundFilteres.pcm", std::ios::out | std::ios::binary);
int i = 0;
bool isDataInBuffer = true;
U u;
while (in.good())
{
    int j = 0;
    for (int i = 0; i < 1024 * 2; i+=2)
    {
        if (false == in.good() && j < 1024) // padd with zeroes
        {
            audioDataBuffer[j] = 0;
        }
        in.read((char*)&audioDataBuffer[j], 2);
        cout << audioDataBuffer[j];
        ++j;
    }
    // Algorithm
    double RealX [1024] = {0};
    double ImmX [1024] = {0};
    ShortArrayToDoubleArray(audioDataBuffer, RealX, 1024);

    // convert to frequency domain
    ForwardRealFFT(RealX, ImmX, 1024);
    // cut frequences < 300 && > 3400
    for (int i = 0; i < 512; ++i)
    {
        if (i * 8000 / 1024 > 3400 || i * 8000 / 1024 < 300 )
        {
            RealX[i] = 0;
            ImmX[i] = 0;
        }
    }
    ReverseRealFFT(RealX, ImmX, 1024);
    DoubleArrayToShortArray(RealX, audioDataBuffer, 1024);
    for (int i = 0; i < 1024; ++i) // 7 6 5 4 3 2 1 0 - byte order hence we write ch[1]  then ch[0]
    {
        u.sh = audioDataBuffer[i];
        out.write(&u.ch[1], 1);
        out.write(&u.ch[0], 1);
    }
}
in.close();
out.close();

when I write result to a file, open it audacity and check spectr analysis, and see that high frequences are cut, but low still remains (they starts from 0)

What I am doing wrong?

Here is sound frequency spectr before enter image description here

Here is sound frequency after I zeroed needed values enter image description here

Please help!

Update:

Here is code I came up with, what I should padd with Zeroes???

void DeleteFrequencies(short *audioDataBuffer, const int bufferSize, int lowestFrequency, int highestFrequency, int sampleRate )
{
    // FFT must be the same length as output segment - to prevent circular convultion
    // 
    int frequencyInHzPerSample = sampleRate / bufferSize;
    /*             __________________________
    /* ___________                           __________________________  filter kernel   */
    int nOfPointsInFilterKernel = (lowestFrequency / frequencyInHzPerSample) + ( bufferSize - highestFrequency / frequencyInHzPerSample);
    U u;
    double *RealX = new  double[bufferSize];
    double *ImmX = new  double[bufferSize];
    ShortArrayToDoubleArray(audioDataBuffer, RealX, bufferSize);

    // padd with zeroes, so that inputSignalSamplesNumber + kernelLength - 1 = bufferSize

    // convert to frequency domain
    ForwardRealFFT(RealX, ImmX, bufferSize);
    // cut frequences < 300 && > 3400
    int Multiplyer = 1;
    for (int i = 0; i < 512; ++i)
    {
        /*if (i * 8000 / 1024 > 3400 || i * 8000 / bufferSize < 300 )
        {
            RealX[i] = 0;
            ImmX[i] = 0;
        }*/
        if (i < lowestFrequency / frequencyInHzPerSample || i > highestFrequency / frequencyInHzPerSample )
            Multiplyer = 0;
        else 
            Multiplyer = 1;
        RealX[i] = RealX[i] * Multiplyer /*ReH[f]*/  - ImmX[i] * Multiplyer;
        ImmX[i] = ImmX[i] * Multiplyer + RealX[i] * Multiplyer;

    }
    ReverseRealFFT(RealX, ImmX, bufferSize);
    DoubleArrayToShortArray(RealX, audioDataBuffer, bufferSize);
    delete [] RealX;
    delete [] ImmX;
}

it produce the following spectrum (low frequencies are cut, but high not) enter image description here

void ForwardRealFFT(double* RealX, double* ImmX, int nOfSamples)
{

short nh, i, j, nMinus1, nDiv2, nDiv4Minus1, im, ip, ip2, ipm, nOfCompositionSteps, LE, LE2, jm1;
double ur, ui, sr, si, tr, ti;

// Step 1 : separate even from odd points
nh = nOfSamples / 2 - 1; 
for (i = 0; i <= nh; ++i)
{
    RealX[i] = RealX[2*i];
    ImmX[i] = RealX[2*i + 1];
}
// Step 2: calculate nOfSamples/2 points using complex FFT
// advantage in efficiency, as nOfSamples/2 requires 1/2 of the time as nOfSamples point FFT
nOfSamples /= 2;
ForwardDiscreteFT(RealX, ImmX, nOfSamples );
nOfSamples *= 2;

// Step 3: even/odd frequency domain decomposition
nMinus1 = nOfSamples - 1; 
nDiv2 = nOfSamples / 2;
nDiv4Minus1 = nOfSamples / 4 - 1;
for (i = 1; i <= nDiv4Minus1; ++i)
{
    im = nDiv2 - i;
    ip2 = i + nDiv2;
    ipm = im + nDiv2;
    RealX[ip2] = (ImmX[i] + ImmX[im]) / 2;
    RealX[ipm] = RealX[ip2];
    ImmX[ip2] = -(RealX[i] - RealX[im]) / 2;
    ImmX[ipm] = - ImmX[ip2];
    RealX[i] = (RealX[i] + RealX[im]) / 2;
    RealX[im] = RealX[i];
    ImmX[i] = (ImmX[i] - ImmX[im]) / 2;
    ImmX[im] = - ImmX[i];
}
RealX[nOfSamples * 3 / 4] = ImmX[nOfSamples / 4];
RealX[nDiv2] = ImmX[0];
ImmX[nOfSamples * 3 / 4] = 0;
ImmX[nDiv2] = 0;
ImmX[nOfSamples / 4] = 0;
ImmX[0] = 0;
// 3-rd step: combine the nOfSamples frequency spectra in the exact reverse order
// that the time domain decomposition took place
nOfCompositionSteps = log((double)nOfSamples) / log(2.0);
LE = pow(2.0,nOfCompositionSteps);
LE2 = LE / 2;
ur = 1;
ui = 0;
sr = cos(M_PI/LE2);
si = -sin(M_PI/LE2);
for (j = 1; j <= LE2; ++j)
{
    jm1 = j - 1;
    for (i = jm1; i <= nMinus1; i += LE)
    {
        ip = i + LE2;
        tr = RealX[ip] * ur - ImmX[ip] * ui;
        ti = RealX[ip] * ui + ImmX[ip] * ur;
        RealX[ip] = RealX[i] - tr;
        ImmX[ip] = ImmX[i] - ti;
        RealX[i] = RealX[i] + tr;
        ImmX[i] = ImmX[i] + ti;
    }
    tr = ur;
    ur = tr * sr - ui * si;
    ui = tr * si + ui * sr;
}
}
Community
  • 1
  • 1
spin_eight
  • 3,925
  • 10
  • 39
  • 61
  • There is a dip before 300, but something else gets added there, may be in other parts of your code? – Ashalynd Jun 08 '14 at 00:32
  • @Ashalynd I added full code. In FFT and IFFT I am sure absoultly ,because I applied them consequently to the same input data and get it - so they are implemented correctly. – spin_eight Jun 08 '14 at 00:43
  • could you show `ForwardRealFFT`? I'm wondering if there's an issue with what the function returns and what you zero out. – Pavel Jun 08 '14 at 00:55
  • @Ashalynd also I am sure 100% in i/o operations (I read from file & write to file correctly) – spin_eight Jun 08 '14 at 00:55
  • @Pavel I have added code for FFT – spin_eight Jun 08 '14 at 00:58
  • Note that the data has been re-scaled in the second display, so (for example) you're actually getting about -30 dB at a frequency of 150. – Jerry Coffin Jun 08 '14 at 01:16
  • @ Jerry Coffin , yes I see that, but my concern is that it wasn`t zeroed. I just started learning DSP and this shpere a little complicated to me. I guess that I implemented band pass filter correctly and this diagramm represents just how it works, am I right? – spin_eight Jun 08 '14 at 01:35
  • see http://dsp.stackexchange.com/questions/6220/why-is-it-a-bad-idea-to-filter-by-zeroing-out-fft-bins/6224#6224. – SleuthEye Jun 08 '14 at 02:01
  • @ SleuthEye, thank you very much, that is valueable information for me, I got that if signal is not periodic - my method fails. Could you please suggest easy method what I can use to filter out noise by given spectrum of frequencies, I need to implement it as a part of my work, also can you provide some references? – spin_eight Jun 08 '14 at 02:22

3 Answers3

4

Fast convolution filtering with an FFT/IFFT requires zero padding to at least twice the length of the filter (and usually to the next power of 2 for performance reasons) and then using overlap add or overlap save methods to remove circular convolution artifacts.

user541686
  • 205,094
  • 128
  • 528
  • 886
hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • Sorry, I don`t get how to implement, that your suggested. How I should do zero padding and what is the length of the filter? I really just started learning DSP subject and hence questions that seems obvious to you for me is complicated. Can you provide more detailed answer, or refere me to the source of information where I can read about it – spin_eight Jun 08 '14 at 05:31
  • No I can`t accept your answer, it doesn`t contains enough information for me. Can you explain: I have input 1024 samples based on the sampleRate & length of input I compute length of filter kernel, and what to padd with zeroes??? I shall update my question with the latest code I got, could you please tell what I am doing wrong – spin_eight Jun 08 '14 at 08:39
  • I have added latest code in the update section, could you look it through? – spin_eight Jun 08 '14 at 08:46
2

You may want to have a look at this answer for an explanation for the effects you are observing.

Otherwise, the 'ideal' filter you are trying to achieve is more a mathematical tool than a practical implementation since the rectangular function in the frequency domain (with a zero-transition and infinite stopband attenuation) corresponds to an infinite length impulse response in the time domain.

To obtain a more practical filter, you must first define desired filter characteristics such as transition width and stopband attenuation based on your specific application needs. Based on these specifications, the filter coefficients can be derived using one of various filter design methods such as:

Perhaps the closest to what you're doing is the Window method. Using that method, something as simple as a triangular window can help increase the stopband attenuation, but you may want to experiment with other window choices (many available from the same link). Increasing the window length would help reduce the transition width.

Once you have completed your filter design, you can apply the filter in the frequency-domain using the overlap-add method or the overlap-save method. Using either of these methods, you would split your input signal in chunks of length L, and pad to some convenient size N >= L+M-1, where M is the number of filter coefficients (for example if you have a filter with 42 coefficients, you might choose N = 128, from which L = N-M+1 = 87).

Community
  • 1
  • 1
SleuthEye
  • 14,379
  • 2
  • 32
  • 61
  • Thank you very much for detailed info, very pity that now I have only few hours left for implementation. – spin_eight Jun 09 '14 at 03:05
  • can you tell me whether I correctly calculate boundaries of frequencies (I want to left frequencies in range [300,3700]) – spin_eight Jun 09 '14 at 03:08
  • Sorry to hear, perhaps this new found knowledge could still be useful in the future or for others. To answer your question: for a N-point real-signal FFT which outputs N/2 complex values, the frequency associated with bin `i` is: `(i * sampleRate) / N`. Apart from the roundoffs due to your usage of `int`, your computations look fine (eg. 300Hz fits between bin 38/39, your formula probably gives you 42). – SleuthEye Jun 09 '14 at 12:15
0

After doing the real FFT you get your spectral data twice: Once in the bins from 0 to 512, and a mirror spectrum in the bins 513 to 1024. Your code however only clears the lower spectrum.

Try this:

for (int i = 0; i < 512; ++i)
{
    if (i * 8000 / 1024 > 3400 || i * 8000 / 1024 < 300 )
    {
        RealX[i] = 0;
        ImmX[i] = 0;

        // clear mirror spectrum as well:
        RealX[1023-i] = 0;
        ImmX[1023-i]  = 0;
    }
}

That may help unless your FFT implementation does this step automatically.

Btw, just zeroing out frequency bins like you did is not a good way to do such filters. Expect a very nasty phase response and a lot of ringing in your signal.

Nils Pipenbrinck
  • 83,631
  • 31
  • 151
  • 221
  • thank you for response, but my implementation doesn`t account frequencies higher than nyquist frequency. – spin_eight Jun 08 '14 at 09:16
  • Then give it a try. You will very likely get a much better response. Also: Unless your soundfile is exactly 1024 samples in length you should apply a window function like the Hann window. – Nils Pipenbrinck Jun 08 '14 at 09:19
  • no, I have already tried it. The sound file is much larger than 1024, why I should apply window function? My first aim is to cut off frequencies, that I can`t achive and I don't know that is the issue – spin_eight Jun 08 '14 at 09:24
  • I added version of code which is close to the desired effect, please see added spectrum. But I can`t explain how it worked, also @hotpaw2 mentioned circular convultion issue - is it related in any way to my question, is it applicable? – spin_eight Jun 08 '14 at 09:37