/**
* Contains Linear Prediction Coefficient functions.
*
* @author <a href="mailto:rsingh@cs.cmu.edu">rsingh</a>
* @version 1.0
*/
package edu.cmu.sphinx.frontend.frequencywarp;
import java.util.Arrays;
/**
* Computes the linear predictive model using the Levinson-Durbin algorithm. Linear prediction assumes that a signal can
* be model as a linear combination of previous samples, that is, the current sample x[i] can be modeled as:
* <pre> x[i] = a[0] + a[1] * x[i - 1] + a[2] * x[i - 2] + ... </pre>
* The summation on the right hand side of the equation involves a finite number of terms. The number of previous
* samples used is the order of the linear prediction.
* <p>
* This class also provides a method to compute LPC cepstra, that is, the cepstra computed from LPC coefficients, as
* well as a method to compute the bilinear transformation of the LPC
*/
public class LinearPredictor {
private int order;
private int cepstrumOrder;
private double[] reflectionCoeffs;
private double[] ARParameters;
private double alpha;
private double[] cepstra;
private final double[] bilinearCepstra;
/**
* Constructs a LinearPredictor with the given order.
*
* @param order the order of the LinearPredictor
*/
public LinearPredictor(int order) {
this.order = order;
// Set the rest to null values
reflectionCoeffs = null;
ARParameters = null;
alpha = 0;
cepstra = null;
bilinearCepstra = null;
}
/**
* Method to compute Linear Prediction Coefficients for a frame of speech. Assumes the following
* sign convention:<br> prediction(x[t]) = Sum_i {Ar[i] * x[t-i]}
*
* @param autocor autocorrlation array
* @return the energy of the frame (alpha in the Levinson recursion)
*/
public double[] getARFilter(double[] autocor) {
/* No signal */
if (autocor[0] == 0) {
return null;
}
reflectionCoeffs = new double[order + 1];
ARParameters = new double[order + 1];
double[] backwardPredictor = new double[order + 1];
alpha = autocor[0];
reflectionCoeffs[1] = -autocor[1] / autocor[0];
ARParameters[0] = 1.0;
ARParameters[1] = reflectionCoeffs[1];
alpha *= (1 - reflectionCoeffs[1] * reflectionCoeffs[1]);
for (int i = 2; i <= order; i++) {
for (int j = 1; j < i; j++) {
backwardPredictor[j] = ARParameters[i - j];
}
reflectionCoeffs[i] = 0;
for (int j = 0; j < i; j++) {
reflectionCoeffs[i] -= ARParameters[j] * autocor[i - j];
}
reflectionCoeffs[i] /= alpha;
for (int j = 1; j < i; j++) {
ARParameters[j] += reflectionCoeffs[i] * backwardPredictor[j];
}
ARParameters[i] = reflectionCoeffs[i];
alpha *= (1 - reflectionCoeffs[i] * reflectionCoeffs[i]);
if (alpha <= 0.0) {
return null;
}
}
return ARParameters;
}
/**
* Computes AR parameters from a given set of reflection coefficients.
*
* @param RC double array of reflection coefficients. The RC array must begin at 1 (RC[0] is a dummy value)
* @param lpcorder AR order desired
* @return AR parameters
*/
public double[] reflectionCoeffsToARParameters(double[] RC, int lpcorder) {
double[][] tmp = new double[lpcorder + 1][lpcorder + 1];
order = lpcorder;
reflectionCoeffs = RC.clone();
for (int i = 1; i <= lpcorder; i++) {
for (int m = 1; m < i; m++) {
tmp[i][m] = tmp[i - 1][m] - RC[i] * tmp[i - 1][i - m];
}
tmp[i][i] = RC[i];
}
ARParameters[0] = 1;
for (int m = 1; m <= lpcorder; m++) {
ARParameters[m] = tmp[m][m];
}
return ARParameters;
}
/**
* Computes LPC Cepstra from the AR predictor parameters and alpha using a recursion invented by Oppenheim et al.
* The literature shows the optimal value of cepstral order to be:
*
* <pre>0.75 * LPCorder <= ceporder <= 1.25 * LPCorder</pre>
*
* @param ceporder is the order of the LPC cepstral vector to be computed.
* @return LPC cepstra
*/
public double[] getData(int ceporder) {
int i;
double sum;
if (ceporder <= 0) {
return null;
}
cepstrumOrder = ceporder;
cepstra = new double[cepstrumOrder];
cepstra[0] = Math.log(alpha);
if (cepstrumOrder == 1) {
return cepstra;
}
cepstra[1] = -ARParameters[1];
for (i = 2; i < Math.min(cepstrumOrder, order + 1); i++) {
sum = i * ARParameters[i];
for (int j = 1; j < i; j++) {
sum += ARParameters[j] * cepstra[i - j] * (i - j);
}
cepstra[i] = -sum / i;
}
for (; i < cepstrumOrder; i++) { // Only if cepstrumOrder > order+1
sum = 0;
for (int j = 1; j <= order; j++) {
sum += ARParameters[j] * cepstra[i - j] * (i - j);
}
cepstra[i] = -sum / i;
}
return cepstra;
}
/**
* Computes a bi-linear frequency warped version of the LPC cepstrum from the LPC cepstrum. The recursive algorithm
* used is defined in Oppenheim's paper in Proceedings of IEEE, June 1972 The program has been written using g[x,y]
* = g_o[x,-y] where g_o is the array used by Oppenheim. To handle the reversed array index the recursion has been
* done DOWN the array index.
*
* @param warp is the warping coefficient. For 16KHz speech 0.6 is good valued.
* @param nbilincepstra is the number of bilinear cepstral values to be computed from the linear frequency
* cepstrum.
* @return a bi-linear frequency warped version of the LPC cepstrum
*/
public double[] getBilinearCepstra(double warp, int nbilincepstra) {
double[][] g = new double[nbilincepstra][cepstrumOrder];
// Make a local copy as this gets destroyed
double[] lincep = Arrays.copyOf(cepstra, cepstrumOrder);
bilinearCepstra[0] = lincep[0];
lincep[0] = 0;
g[0][cepstrumOrder - 1] = lincep[cepstrumOrder - 1];
for (int i = 1; i < nbilincepstra; i++) {
g[i][cepstrumOrder - 1] = 0;
}
for (int i = cepstrumOrder - 2; i >= 0; i--) {
g[0][i] = warp * g[0][i + 1] + lincep[i];
g[1][i] = (1 - warp * warp) * g[0][i + 1] + warp * g[1][i + 1];
for (int j = 2; j < nbilincepstra; j++) {
g[j][i] = warp * (g[j][i + 1] - g[j - 1][i]) + g[j - 1][i + 1];
}
}
for (int i = 1; i <= nbilincepstra; i++) {
bilinearCepstra[i] = g[i][0];
}
return bilinearCepstra;
}
}