/** * 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.window; import java.util.Arrays; import marytts.signalproc.process.CopyingDataProcessor; import marytts.signalproc.process.InlineDataProcessor; import marytts.util.math.ArrayUtils; import marytts.util.math.MathUtils; /** * @author Marc Schröder * * Interface for windowing functions. */ public abstract class Window implements CopyingDataProcessor, InlineDataProcessor { public static final int RECT = 0; public static final int HAMMING = 1; public static final int BLACKMAN = 2; public static final int HANNING = 3; public static final int GAUSS = 4; public static final int BARTLETT = 5; public static final int FLATTOP = 6; protected double prescalingFactor; protected boolean evenLength; /** * This array, whose length is the window length, holds the multiplication factors for each sample across the window. It must * be initialised in the constructor, using the method #initialise(). */ protected double[] window; /** * Default constructor for subclasses that need to do something themselves before calling initialise(). * */ protected Window() { prescalingFactor = 1.; } /** * Create a new window of length length. This will call #initialise() in the respective subclass in order to fill the window * array with meaningful multiplication values. * * @param length * the window length, in samples; this should be an odd number, so that the window can be symmetric around the * center point. * @throws IllegalArgumentException * if length is an even number. */ public Window(int length) { this(length, 1); } /** * Create a new window of length length, and apply the given prescaling factor to each point. This will call #initialise() in * the respective subclass in order to fill the window array with meaningful multiplication values. * * @param length * the window length, in samples; this should be an odd number, so that the window can be symmetric around the * center point. * @param prescalingFactor * a factor to apply to each window point. * @throws IllegalArgumentException * if length is an even number. */ public Window(int length, double prescalingFactor) { window = new double[length]; this.evenLength = (length % 2 == 0); this.prescalingFactor = prescalingFactor; initialise(); } /** * Apply this window on the given source data array, at the given position. This method returns the resulting data in a new * array of the same length as this window (@see getLength()). If src has less than getLength() data points left at pos, zeros * are added at the end. * * @param src * the source data array to apply the windowing function to. * @param pos * the position from which to apply the windowing function * @return an array of the same length as this window, computed by applying this window to the source data. * @throws IllegalArgumentException * if targetLength is smaller than this window's length as returned by #getLength(). */ public double[] apply(final double[] src, int pos) { double target[] = new double[window.length]; apply(src, pos, target, 0); return target; } public double[] apply(final double[] src) { return apply(src, 0); } /** * Apply the window function in-line, i.e. by modifying the original data. * * @param data * data * @param pos * the position in the data array where to start applying the window function. * @param len * the amount of data after position pos to process. len must be less than or equal to getLength(). If it is less * than getLength(), a truncated window will be applied. * @throws IllegalArgumentException * if len != getLength(). */ public void applyInline(double[] data, int pos, int len) { apply(data, pos, data, pos, len); } public void applyInline(double[] data, int pos) { applyInline(data, pos, window.length); } public void applyInline(double[] data) { applyInline(data, 0, window.length); } /** * Apply this window on the given source data array, at the given position. This method returns the resulting data in the * given target array, at the target position given by targetPos. If src has less than getLength() data points left at pos, * zeros are added at the end. * * @param src * the source data array to apply the windowing function to. * @param srcPos * the position in the source array from which to apply the windowing function * @param target * an array to receive the target data, computed by applying this window to the source data. The target array must * be long enough to receive getLength() bytes after targetPos. if target == source and targetPos == srcPos, then * the window function is applied in-place. * @param targetPos * targetPos * @throws IllegalArgumentException * if target.length-targetPos is smaller than this window's length as returned by #getLength(). */ public void apply(final double[] src, int srcPos, double[] target, int targetPos) { apply(src, srcPos, target, targetPos, 0, window.length); } /** * Apply this window on the given source data array, at the given position. This method returns the resulting data in the * given target array, at the target position given by targetPos. If src has less than getLength() data points left at pos, * zeros are added at the end. * * @param src * the source data array to apply the windowing function to. * @param srcPos * the position in the source array from which to apply the windowing function. If srcPos is negative, abs(srcPos) * zeroes will be pre-pended before the first data from src is taken into account; if it is greater than * src.length-getLength(), the result will be filled up with trailing zeroes behind the last data. * @param target * an array to receive the target data, computed by applying this window to the source data. The target array must * be long enough to receive getLength() bytes after targetPos. if target == source and targetPos == srcPos, then * the window function is applied in-place. * @param targetPos * target Pos * @param len * the number of samples of the window to apply; this must be less than or equal getLength(). If it is less than * getLength(), a truncated window will be applied. * @throws IllegalArgumentException * if target.length-targetPos is smaller than this window's length as returned by #getLength(), or if len is * greater or equals to getLength. */ public void apply(final double[] src, int srcPos, double[] target, int targetPos, int len) { apply(src, srcPos, target, targetPos, 0, len); } /** * Apply a part of this window on the given source data array, at the given position. For example, by setting off to * getLength()/2 and len to getLength()/2, only the right half of the window will be applied. This method returns the * resulting data in the given target array, at the target position given by targetPos. If src has less than len data points * left at srcPos, zeros are added at the end. * * @param src * the source data array to apply the windowing function to. * @param srcPos * the position in the source array from which to apply the windowing function. If srcPos is negative, abs(srcPos) * zeroes will be pre-pended before the first data from src is taken into account; if it is greater than * src.length-getLength(), the result will be filled up with trailing zeroes behind the last data. * @param target * an array to receive the target data, computed by applying this window to the source data. The target array must * be long enough to receive getLength() bytes after targetPos. if target == source and targetPos == srcPos, then * the window function is applied in-place. * @param targetPos * targetPos * @param off * the offset from the start of the window from where on the window is to be applied. * @param len * the number of samples of the window to apply; off+len must be less than or equal getLength(). * @throws IllegalArgumentException * if target.length-targetPos is smaller than this window's length as returned by #getLength(), or if len is * greater or equals to getLength. */ public void apply(final double[] src, int srcPos, double[] target, int targetPos, int off, int len) { if (len < 0 || off < 0 || off + len > window.length) throw new IllegalArgumentException("Requested offset " + off + " or length " + len + " does not fit into window length " + window.length); if (target.length < targetPos + len) throw new IllegalArgumentException("Target array cannot hold enough data"); int start, end; // actual positions in src to apply the window to. // If these deviate from srcPos and srcPos+len, resp., zeroes need to be pre-/appended. start = srcPos; if (start < 0) { start = 0; Arrays.fill(target, targetPos, targetPos + (start - srcPos), 0); } end = srcPos + len; if (end > src.length) { end = src.length; Arrays.fill(target, targetPos + end - srcPos, targetPos + len, 0); } for (int i = start; i < end; i++) { target[targetPos + i - srcPos] = src[i] * window[off + i - srcPos]; } } /** * The initialisation of the window array with multiplication factors corresponding to the specific windowing function. This * needs to be implemented by each subclass. */ protected abstract void initialise(); /** * Return the length of this window, in samples. * * @throws NullPointerException * if the window has not yet been initialised. * @return window length */ public int getLength() { if (window == null) { throw new NullPointerException("The window has not yet been initialised"); } return window.length; } /** * Get the value of the window function at index position i * * @param i * the index position in the window for which to return the value * @return the value of the window function, between 0 and 1. * @throws IllegalArgumentException * if i<0 or i>getLength(). * @throws NullPointerException * if the window has not yet been initialised. */ public double value(int i) { if (window == null) { throw new NullPointerException("The window has not yet been initialised"); } if (i < 0 || i > window.length) { throw new IllegalArgumentException("Can only return values for index positions 0 to " + window.length); } return window[i]; } /** * Return this window's type, as defined by the constants in Window, or -1 if the window is not of a known type. * * @return -1 */ public int type() { if (this instanceof RectWindow) return RECT; else if (this instanceof HammingWindow) return HAMMING; else if (this instanceof BlackmanWindow) return BLACKMAN; else if (this instanceof HanningWindow) return HANNING; else if (this instanceof GaussWindow) return GAUSS; else if (this instanceof BartlettWindow) return BARTLETT; else if (this instanceof FlattopWindow) return FLATTOP; else return -1; } /** * Convenience method for requesting a window of the requested type. * * @param windowType * one of the constants defined in Window. * @param length * window length (should be an odd number) * @return a window of the requested type and length * @throws IllegalArgumentException * if windowType is not a valid window type, or if length is an even number */ public static Window get(int windowType, int length) { return get(windowType, length, 1.); } /** * Convenience method for requesting a window of the requested type. * * @param windowType * one of the constants defined in Window. * @param length * window length (should be an odd number) * @param prescale * a prescaling factor applied to all points in the window * @return a window of the requested type and length * @throws IllegalArgumentException * if windowType is not a valid window type, or if length is an even number */ public static Window get(int windowType, int length, double prescale) { switch (windowType) { case RECT: return new RectWindow(length, prescale); case HAMMING: return new HammingWindow(length, prescale); case BLACKMAN: return new BlackmanWindow(length, prescale); case HANNING: return new HanningWindow(length, prescale); case GAUSS: return new GaussWindow(length, prescale); case BARTLETT: return new BartlettWindow(length, prescale); case FLATTOP: return new FlattopWindow(length, prescale); default: throw new IllegalArgumentException("Unknown window type requested."); } } /** * List all available window types * * @return an integer corresponding to the constants defined in Window. */ public static int[] getAvailableTypes() { return new int[] { RECT, HAMMING, BLACKMAN, HANNING, GAUSS, BARTLETT, FLATTOP }; } /** * For a given type name (e.g., "Hanning window", or "BARTLETT"), return the type code. Matching is done as case-insensitive * prefix matching. * * @param typeName * the type name. * @return the type code corresponding to typeName, or -1 if none could be determined. */ public static int getTypeByName(String typeName) { String tomatch = typeName.toUpperCase(); if (tomatch.startsWith("HAMMING")) return HAMMING; else if (tomatch.startsWith("RECT")) return RECT; else if (tomatch.startsWith("BLACKMAN")) return BLACKMAN; else if (tomatch.startsWith("HANNING")) return HANNING; else if (tomatch.startsWith("GAUSS")) return GAUSS; else if (tomatch.startsWith("BARTLETT")) return BARTLETT; else if (tomatch.startsWith("FLATTOP")) return FLATTOP; else return -1; } /** * Get the type name of a window type. * * @param windowType * a valid window type * @return a string representing the type name * @throws IllegalArgumentException * if windowType is not a valid window type */ public static String getTypeName(int windowType) { Window w = get(windowType, 1); return w.toString(); } // Normalize window coefficients to sum up to unity public void normalize() { normalize(1.0f); } // Normalize window coefficients to sum up to val public void normalizePeakValue(float desiredPeakValue) { double maxVal = MathUtils.getMax(window); double scale = desiredPeakValue / maxVal; for (int i = 0; i < window.length; i++) window[i] *= scale; } // Normalize window coefficients to sum up to val public void normalize(float val) { float total = 0.0f; int i; for (i = 0; i < window.length; i++) total += window[i]; float scale = val / total; for (i = 0; i < window.length; i++) window[i] *= scale; } // Normalize window coefficients such that squared sum of coefficients is equal to val public void normalizeSquaredSum(float val) { float total = 0.0f; int i; for (i = 0; i < window.length; i++) total += window[i] * window[i]; float scale = (float) (Math.sqrt(val) / Math.sqrt(total)); for (i = 0; i < window.length; i++) window[i] *= scale; } public void normalizeRange(float minVal, float maxVal) { MathUtils.adjustRange(window, minVal, maxVal); } public double[] getCoeffs() { return window; } public double[] getCoeffsLeftHalf() { return ArrayUtils.subarray(window, 0, (int) Math.floor(0.5 * window.length + 0.5)); } public double[] getCoeffsRightHalf() { int off = (int) Math.floor(0.5 * window.length + 0.5); int len = window.length - off; return ArrayUtils.subarray(window, off, len); } }