DSP tutorial: Lowpass filtering using FFT
Last modified: July 26th, 2013This example records audio and saves the lowpass filtered data to a .wav file. There’s also an example of bandpass filtering. Filtering is done with FFT.
Note: this simple brickwall filtering method is not applicable in some cases because it introduces ringing in the resulting filtered sound. See this section of my tutorial to read about this more.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | public class LowPassFilter implements Runnable { private final static int SAMPLERATE = 44100; private final static int BUFFERSIZE = SAMPLERATE * 2; private TargetDataLine tdl; private DoubleFFT_1D fft; LowPassFilter(TargetDataLine tdl) { this.tdl = tdl; } // converts float array to byte array private byte[] getBytesFromDoubles(final double[] audioData, final int storedSamples) { byte[] audioDataBytes = new byte[storedSamples * 2]; for (int i = 0; i < storedSamples; i++) { // saturation audioData[i] = Math.min(1.0, Math.max(-1.0, audioData[i])); // scaling and conversion to integer int sample = (int) Math.round((audioData[i] + 1.0) * 32767.5) - 32768; byte high = (byte) ((sample >> 8) & 0xFF); byte low = (byte) (sample & 0xFF); audioDataBytes[i * 2] = low; audioDataBytes[i * 2 + 1] = high; } return audioDataBytes; } // saves the audio data given in audioDataBytes to a .wav file private void writeWavFile(final byte[] audioDataBytes, final int storedSamples, final String fileName) { AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, SAMPLERATE, 16, 1, 2, SAMPLERATE, false); AudioInputStream audioInputStream = new AudioInputStream(new ByteArrayInputStream(audioDataBytes), audioFormat, storedSamples); try { FileOutputStream fileOutputStream = new FileOutputStream(fileName); AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, fileOutputStream); audioInputStream.close(); } catch (Exception e) { e.printStackTrace(); } } private void lowPassFilter(double[] audioData, final int storedSamples) { // we need to initialize a buffer where we store our samples as complex numbers. first value is the real part, second is the imaginary. double[] fftData = new double[audioData.length * 2]; for (int i = 0; i < storedSamples; i++) { // copying audio data to the fft data buffer, imaginary part is 0 fftData[2 * i] = audioData[i]; fftData[2 * i + 1] = 0; } // calculating the fft of the data, so we will have spectral power of each frequency component fft.complexForward(fftData); // zeroing out all components above this frequency int cutFreq = 2000; for (int i = 0; i < fftData.length; i += 2) { // lowpass if (i > ((cutFreq * (fftData.length/2)) / SAMPLERATE)*2) fftData[i] = fftData[i + 1] = 0; } // bandpass //if (i < (((cutFreq - 1000) * (fftData.length/2)) / SAMPLERATE)*2 || i > (((cutFreq + 1000) * (fftData.length/2)) / SAMPLERATE)*2) //fftData[i] = fftData[i + 1] = 0; // built-in scaling hangs the thread, so we don't use it fft.complexInverse(fftData, false); for (int i = 0; i < storedSamples; i++) { audioData[i] = fftData[2 * i] / (SAMPLERATE/4.0); // applying scaling } } @Override public void run() { byte[] abBuffer = new byte[tdl.getBufferSize()]; double[] abBufferDouble = new double[abBuffer.length / 2]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); // this will store sound data fft = new DoubleFFT_1D(abBufferDouble.length); tdl.start(); try { while (!Thread.interrupted()) { // waiting for the buffer to get filled while (tdl.available() < tdl.getBufferSize() * 0.5) Thread.sleep(0, 1); // without this, the audio will be choppy int bytesRead = tdl.read(abBuffer, 0, tdl.available()); // converting frames stored as bytes to double values int samplesRead = bytesRead / tdl.getFormat().getFrameSize(); for (int i = 0; i < samplesRead; i++) abBufferDouble[i] = ((abBuffer[i * 2] & 0xFF) | (abBuffer[i * 2 + 1] << 8)) / 32768.0; lowPassFilter(abBufferDouble, samplesRead); baos.write(getBytesFromDoubles(abBufferDouble, samplesRead), 0, samplesRead * 2); } } catch (InterruptedException e) { } tdl.stop(); tdl.close(); writeWavFile(baos.toByteArray(), baos.size() / 2, "output.wav"); } public static void main(String[] args) { AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, SAMPLERATE, 16, 1, 2, SAMPLERATE, false); DataLine.Info info = new DataLine.Info(TargetDataLine.class, audioFormat, BUFFERSIZE); TargetDataLine targetDataLine = null; try { targetDataLine = (TargetDataLine) AudioSystem.getLine(info); targetDataLine.open(audioFormat, BUFFERSIZE); System.out.println("Buffer size: " + targetDataLine.getBufferSize()); } catch (LineUnavailableException e1) { e1.printStackTrace(); } // creating the recorder thread from this class' instance LowPassFilter lowPassFilter = new LowPassFilter(targetDataLine); Thread lowPassFilterThread = new Thread(lowPassFilter); // we use this to read line from the standard input BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); lowPassFilterThread.setPriority(Thread.MAX_PRIORITY); lowPassFilterThread.start(); System.out.println("Recording... press ENTER to stop recording!"); try { br.readLine(); } catch (IOException e) { e.printStackTrace(); } lowPassFilterThread.interrupt(); try { // waiting for the recorder thread to stop lowPassFilterThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Recording stopped."); } } |
download (850.3 kb)
RSS feed for comments
Trackback URL
Trackback URL
2 Comments »
Trackback responses to this post
About me
I'm Nonoo. This is my blog about music, sounds, filmmaking, amateur radio, computers, programming, electronics and other things I'm obsessed with.
... »
hey thanks for the tutorial, helping me a bunch, just wanted to point out a broken link on this page:
http://dp.nonoo.hu/projects/ham-dsp-tutorial/07-fft-lowpass-filtering/
the link under the text ‘this section’ points to
http://dp.nonoo.hu/projects/ham-dsp-tutorial/19-dsp-tutorial-lowpass-fir-filtering-using-fft-convolution/
but I think it needs to point to
http://dp.nonoo.hu/projects/ham-dsp-tutorial/19-lowpass-fir-filtering-using-fft-convolution/
cheers!
Thanks, I fixed it!