/** * Copyright 2004-2006 DFKI GmbH. * All Rights Reserved. Use is subject to license terms. * * This file is part of MARY TTS. * * MARY TTS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package marytts.signalproc.filter; import marytts.signalproc.process.FrameProvider; import marytts.signalproc.process.InlineDataProcessor; import marytts.util.data.BlockwiseDoubleDataSource; import marytts.util.data.BufferedDoubleDataSource; import marytts.util.data.DoubleDataSource; import marytts.util.data.SequenceDoubleDataSource; import marytts.util.math.FFT; import marytts.util.math.MathUtils; /** * @author Marc Schröder A filter corresponding to a finite-impulse-response LTI system. The filtering of the input signal * corresponds to a convolution with the impulse response of the system. */ public class FIRFilter implements InlineDataProcessor { protected double[] transformedIR; protected int impulseResponseLength; protected int sliceLength; protected double[] denumeratorCoefficients; // Digital filter coefficients for time domain digital filtering /** * Create a new, uninitialised FIR filter. Subclasses need to call * * check {@link #initialise(double[] impulseResponse, int sliceLen)} . */ protected FIRFilter() { } /** * Create a new Finite Impulse Response filter. * * @param impulseResponse * the impulse response signal */ public FIRFilter(double[] impulseResponse) { int sliceLen = MathUtils.closestPowerOfTwoAbove(2 * impulseResponse.length) - impulseResponse.length; initialise(impulseResponse, sliceLen); } public FIRFilter(double[] impulseResponse, int len) { initialise(impulseResponse, len); } /** * Initialise the Finite Impulse Response filter. * * @param impulseResponse * the impulse response signal * @param sliceLen * the length of the slices in which to process the input data. IMPORTANT: impulseResponse.length+sliceLength must * be a power of two (256, 512, etc.) * @throws IllegalArgumentException * if the slice length is shorter than the impulse response length, or it the sum of both lengths is not a power * of two. */ protected void initialise(double[] impulseResponse, int sliceLen) { denumeratorCoefficients = new double[impulseResponse.length]; System.arraycopy(impulseResponse, 0, denumeratorCoefficients, 0, impulseResponse.length); if (!MathUtils.isPowerOfTwo(impulseResponse.length + sliceLen)) throw new IllegalArgumentException("Impulse response length plus slice length must be a power of two"); this.impulseResponseLength = impulseResponse.length; this.sliceLength = sliceLen; transformedIR = new double[sliceLen + impulseResponse.length]; System.arraycopy(impulseResponse, 0, transformedIR, 0, impulseResponse.length); FFT.realTransform(transformedIR, false); // This means, we are not actually saving the impulseResponse, but only // its complex FFT transform. } /** * Apply this filter to the given input signal. The input signal is filtered piece by piece, as it is read from the data * source returned by this method. This is the recommended way to filter longer signals. * * @param signal * the signal to which this filter should be applied * @return a DoubleDataSource from which the data can be read */ public DoubleDataSource apply(DoubleDataSource signal) { return new FIROutput(signal); } /** * Apply this filter to the given input signal. This method filters the entire signal, and returns the entire filtered signal. * For long signals, it is better to use apply(DoubleDataSource). * * @param signal * the signal to which this filter should be applied * @return the filtered signal. */ public double[] apply(double[] signal) { return new FIROutput(new BufferedDoubleDataSource(signal)).getAllData(); } public class FIROutput extends BlockwiseDoubleDataSource { protected FrameProvider frameProvider; protected int nTailCutoff; public FIROutput(DoubleDataSource inputSource) { super(null, sliceLength); int samplingRate = 1; // unknown int frameLength = sliceLength + impulseResponseLength; assert MathUtils.isPowerOfTwo(frameLength); // Need to start with zero padding of length impulseResponseLength: DoubleDataSource padding = new BufferedDoubleDataSource(new double[impulseResponseLength]); DoubleDataSource paddedSource = new SequenceDoubleDataSource(new DoubleDataSource[] { padding, inputSource }); this.frameProvider = new FrameProvider(paddedSource, null, frameLength, sliceLength, 1, false); // discard the initial padding of impulseResponseLength/2: int nHeadCutoff = impulseResponseLength / 2; nTailCutoff = impulseResponseLength - nHeadCutoff; getData(nHeadCutoff); // and discard } @Override public boolean hasMoreData() { return currentlyInBuffer() > 0 || frameProvider.hasMoreData(); } /** * This implementation of getData() will cut off a tail corresponding to half of the FIR filter. */ @Override public int getData(double[] target, int targetPos, int length) { // if (target.length < targetPos+length) // throw new IllegalArgumentException("Not enough space left in target array"); int toRead = length + nTailCutoff; if (currentlyInBuffer() < toRead) { // first need to try and read some more data readIntoBuffer(toRead - currentlyInBuffer()); } int toDeliver = length; if (currentlyInBuffer() < toRead) { toDeliver = currentlyInBuffer() - nTailCutoff; writePos -= nTailCutoff; } System.arraycopy(buf, readPos, target, targetPos, toDeliver); readPos += toDeliver; assert readPos <= writePos; return toDeliver; } /** * Try to get a block of getBlockSize() doubles from this DoubleDataSource, and copy them into target, starting from * targetPos. * * @param target * the double array to write into * @param targetPos * position in target where to start writing * @return the amount of data actually delivered. If 0 is returned, all further calls will also return 0 and not copy * anything. */ @Override protected int readBlock(double[] target, int targetPos) { double[] frame = frameProvider.getNextFrame(); if (frame == null) { // already finished processing return 0; } assert blockSize <= frameProvider.getFrameLengthSamples(); assert blockSize == frameProvider.getFrameShiftSamples(); // Now do the convolution: double[] convResult = FFT.convolve_FD(frame, transformedIR); int toCopy = blockSize; if (frameProvider.validSamplesInFrame() < blockSize) toCopy = frameProvider.validSamplesInFrame(); // System.err.println("Copying " + toCopy); // Overlap-save approach: // always ignore the first "impulseResponseLength" samples in convResult, // because they are contaminated due to circular convolution: System.arraycopy(convResult, impulseResponseLength, target, targetPos, toCopy); return toCopy; } } public void applyInline(double[] data, int off, int len) { double[] dataOut = apply(data); System.arraycopy(dataOut, 0, data, 0, len); } public int getImpulseResponseLength() { return impulseResponseLength; } public double[] getDenumeratorCoefficients() { return denumeratorCoefficients; } }