/// <summary> /// Compute Mel Frequency Filters /// </summary> /// <param name="winsize">the window size</param> /// <param name="sampleRate">the sample rate</param> /// <param name="numberFilters">number of filters in the filterbank</param> /// <param name="minFreq">lowest frequency in the range of interest</param> public MelFilter(int winsize, int sampleRate, int numberFilters, int minFreq) { // arrays to store the mel frequencies and the herz frequencies var mel = new double[sampleRate / 2 - minFreq + 1]; var freq = new double[sampleRate / 2 - minFreq + 1]; // Mel Scale from StartFreq to SamplingRate/2, step every 1Hz for (int f = minFreq; f <= sampleRate / 2; f++) { mel[f - minFreq] = MelUtils.LinToMelFreq(f); freq[f - minFreq] = f; } // prepare filters var freqs = new double[numberFilters + 2]; melScaleFreqsIndex = new int[numberFilters + 2]; for (int f = 0; f < freqs.Length; f++) { double melIndex = 1.0 + ((mel[mel.Length - 1] - 1.0) / (freqs.Length - 1.0) * f); double min = Math.Abs(mel[0] - melIndex); freqs[f] = freq[0]; for (int j = 1; j < mel.Length; j++) { double cur = Math.Abs(mel[j] - melIndex); if (cur < min) { min = cur; freqs[f] = freq[j]; } } melScaleFreqsIndex[f] = MathUtils.FreqToIndex(freqs[f], sampleRate, winsize); } // triangle heights melScaleTriangleHeights = new double[numberFilters]; for (int j = 0; j < melScaleTriangleHeights.Length; j++) { // Let's make the filter area equal to 1. // filterHeight = 2.0 / (rightEdge - leftEdge); melScaleTriangleHeights[j] = 2.0 / (freqs[j + 2] - freqs[j]); } var fftFreq = new double[winsize / 2 + 1]; for (int j = 0; j < fftFreq.Length; j++) { fftFreq[j] = ((sampleRate / 2) / (fftFreq.Length - 1.0)) * j; } // Compute the mel filter Weights filterWeights = new Matrix(numberFilters, winsize / 2); for (int j = 0; j < numberFilters; j++) { for (int k = 0; k < fftFreq.Length; k++) { if ((fftFreq[k] > freqs[j]) && (fftFreq[k] <= freqs[j + 1])) { filterWeights.MatrixData[j][k] = (melScaleTriangleHeights[j] * ((fftFreq[k] - freqs[j]) / (freqs[j + 1] - freqs[j]))); } if ((fftFreq[k] > freqs[j + 1]) && (fftFreq[k] < freqs[j + 2])) { filterWeights.MatrixData[j][k] += (melScaleTriangleHeights[j] * ((freqs[j + 2] - fftFreq[k]) / (freqs[j + 2] - freqs[j + 1]))); } } } }
/// <summary> /// Creates a new MelFilterBank. /// </summary> /// <param name="minFreq">The minimum frequency in hz to be considered, i.e. /// the left edge of the first TriangleFilter.</param> /// <param name="maxFreq">The maximum frequency in hz to be considered, i.e. /// the right edge of the last TriangleFilter.</param> /// <param name="numMelBands">The number of Mel bands to be calculated, i.e. /// the number of TriangleFilters to be applied.</param> /// <param name="numBins">The number of bins that are present in the fft_buffer /// that will be passed to the MelFilterBank.Apply method. This is also /// required to properly configure the TriangleFilter instances which /// operate on array indices only. (half the window size)</param> /// <param name="sampleRate">The original sample rate the FFT buffer which will /// be passed to MelFilterBank.Apply is based on.</param> /// <param name="doNormalizeFilterArea">If set to "true", the area of the /// created TriangleFilter will be normalized, e.g. the height of the /// filter's triangle shape will be configured in a way, that the area /// of the triangle shape equals one.</param> public MelFilterBank(double minFreq, double maxFreq, int numMelBands, int numBins, int sampleRate, bool doNormalizeFilterArea) { this.minFreq = minFreq; this.maxFreq = maxFreq; this.numMelBands = numMelBands; this.numBins = numBins; this.sampleRate = sampleRate; this.doNormalizeFilterArea = doNormalizeFilterArea; // Let's do some argument checking if ((minFreq >= maxFreq) || (maxFreq == 0)) { throw new ArgumentException(String.Format("Invalid min/max frequencies for MelFilterBank: min = '{0}' max = '{1}'", minFreq, maxFreq)); } if (numMelBands == 0) { throw new ArgumentException(String.Format("Invalid number of mel bands for MelFilterBank: n = {0}", numMelBands)); } if (sampleRate == 0) { throw new ArgumentException(String.Format("Invalid sample rate for MelFilterBank: s = {0}", sampleRate)); } if (numBins == 0) { throw new ArgumentException(String.Format("Invalid number of bins for MelFilterBank: s = '{0}'", numBins)); } // 2 * numBins should be the same as window length double deltaFreq = (double)sampleRate / (2 * numBins); double melMin = MelUtils.LinToMelFreq(minFreq); double melMax = MelUtils.LinToMelFreq(maxFreq); // We divide by #band + 1 as min / max should present the beginning / end // of beginng up / ending low slope, i.e. it's not the centers of each // band that represent min/max frequency in mel bands. double deltaFreqMel = (melMax - melMin) / (numMelBands + 1); // Fill up equidistant spacing in mel-space double melLeft = melMin; for (int i = 0; i < numMelBands; i++) { double melCenter = melLeft + deltaFreqMel; double melRight = melCenter + deltaFreqMel; double leftHz = MelUtils.MelToLinFreq(melLeft); double rightHz = MelUtils.MelToLinFreq(melRight); // align to closest num_bins (round) int leftBin = (int)((leftHz / deltaFreq) + 0.5); int rightBin = (int)((rightHz / deltaFreq) + 0.5); // calculate normalized height double height = 1.0; if (doNormalizeFilterArea) { height = 2.0 / (rightBin - leftBin); } // Create the actual filter var filter = new TriangleFilter(leftBin, rightBin, height); filters.Add(filter); // next left edge is current center melLeft = melCenter; } }