/* * Copyright 1999-2002 Carnegie Mellon University. * Portions Copyright 2002 Sun Microsystems, Inc. * Portions Copyright 2002 Mitsubishi Electric Research Laboratories. * All Rights Reserved. Use is subject to license terms. * * See the file "license.terms" for information on usage and * redistribution of this file, and for a DISCLAIMER OF ALL * WARRANTIES. * */ package edu.cmu.sphinx.frontend.frequencywarp; /** * Defines a triangular mel-filter. The {@link edu.cmu.sphinx.frontend.frequencywarp.MelFrequencyFilterBank} creates * mel-filters and filters spectrum data. * <p> * A mel-filter is a triangular shaped bandpass filter. When a mel-filter is constructed, the parameters * <code>leftEdge</code>, <code>rightEdge</code>, <code>centerFreq</code>, <code>initialFreq</code>, and * <code>deltaFreq</code> are given to the {@link MelFilter Constructor}. The first three arguments to the constructor, * i.e. <code>leftEdge</code>, <code>rightEdge</code>, and <code>centerFreq</code>, specify the filter's slopes. The * total area under the filter is 1. The filter is shaped as a triangle. Knowing the distance between the center * frequency and each of the edges, it is easy to compute the slopes of the two sides in the triangle - the third side * being the frequency axis. The last two arguments, <code>initialFreq</code> and <code>deltaFreq</code>, identify the * first frequency bin that falls inside this filter and the spacing between successive frequency bins. All frequencies * here are considered in a linear scale. * <p> * Figure 1 below shows pictorially what the other parameters mean. * <p> * <img alt="Mel filter" src="doc-files/melfilter.jpg"> <br><center><b>Figure 1: A triangular mel-filter.</b></center> * * @see MelFrequencyFilterBank */ public class MelFilter { private double[] weight; private int initialFreqIndex; /** * Constructs a filter from the parameters. * <p> * In the current implementation, the filter is a bandpass filter with a triangular shape. We're given the left and * right edges and the center frequency, so we can determine the right and left slopes, which could be not only * asymmetric but completely different. We're also given the initial frequency, which may or may not coincide with * the left edge, and the frequency step. * * @param leftEdge the filter's lowest passing frequency * @param centerFreq the filter's center frequency * @param rightEdge the filter's highest passing frequency * @param initialFreq the first frequency bin in the pass band * @param deltaFreq the step in the frequency axis between frequency bins * @throws IllegalArgumentException if input is invalid */ public MelFilter(double leftEdge, double centerFreq, double rightEdge, double initialFreq, double deltaFreq) throws IllegalArgumentException { double filterHeight; double leftSlope; double rightSlope; double currentFreq; int indexFilterWeight; int numberElementsWeightField; if (deltaFreq == 0) { throw new IllegalArgumentException("deltaFreq has zero value"); } /** * Check if the left and right boundaries of the filter are * too close. */ if ((Math.round(rightEdge - leftEdge) == 0) || (Math.round(centerFreq - leftEdge) == 0) || (Math.round(rightEdge - centerFreq) == 0)) { throw new IllegalArgumentException("Filter boundaries too close"); } /** * Let's compute the number of elements we need in the * <code>weight</code> field by computing how many frequency * bins we can fit in the current frequency range. */ numberElementsWeightField = (int) Math.round((rightEdge - leftEdge) / deltaFreq + 1); /** * Initialize the <code>weight</code> field. */ if (numberElementsWeightField == 0) { throw new IllegalArgumentException("Number of elements in mel" + " is zero."); } weight = new double[numberElementsWeightField]; /** * Let's make the filter area equal to 1. */ filterHeight = 2.0f / (rightEdge - leftEdge); /** * Now let's compute the slopes based on the height. */ leftSlope = filterHeight / (centerFreq - leftEdge); rightSlope = filterHeight / (centerFreq - rightEdge); /** * Now let's compute the weight for each frequency bin. We * initialize and update two variables in the <code>for</code> * line. */ for (currentFreq = initialFreq, indexFilterWeight = 0; currentFreq <= rightEdge; currentFreq += deltaFreq, indexFilterWeight++) { /** * A straight line that contains point <b>(x0, y0)</b> and * has slope <b>m</b> is defined by: * * <b>y = y0 + m * (x - x0)</b> * * This is used for both "sides" of the triangular filter * below. */ if (currentFreq < centerFreq) { weight[indexFilterWeight] = leftSlope * (currentFreq - leftEdge); } else { weight[indexFilterWeight] = filterHeight + rightSlope * (currentFreq - centerFreq); } } /** * Initializing frequency related fields. */ this.initialFreqIndex = (int) Math.round (initialFreq / deltaFreq); } /** * Compute the output of a filter. We're given a power spectrum, to which we apply the appropriate weights. * * @param spectrum the input power spectrum to be filtered * @return the filtered value, in fact a weighted average of power in the frequency range of the filter pass band */ public double filterOutput(double[] spectrum) { double output = 0.0f; int indexSpectrum; for (int i = 0; i < this.weight.length; i++) { indexSpectrum = this.initialFreqIndex + i; if (indexSpectrum < spectrum.length) { output += spectrum[indexSpectrum] * this.weight[i]; } } return output; } }