summaryrefslogtreecommitdiff
path: root/src/sms/cepstrum.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sms/cepstrum.c')
-rw-r--r--src/sms/cepstrum.c259
1 files changed, 259 insertions, 0 deletions
diff --git a/src/sms/cepstrum.c b/src/sms/cepstrum.c
new file mode 100644
index 0000000..8a56787
--- /dev/null
+++ b/src/sms/cepstrum.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2008 MUSIC TECHNOLOGY GROUP (MTG)
+ * UNIVERSITAT POMPEU FABRA
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+/*! \file cepstrum.c
+ * \brief routines for different Fast Fourier Transform Algorithms
+ *
+ */
+
+#include "sms.h"
+#include <gsl/gsl_matrix.h>
+#include <gsl/gsl_linalg.h>
+#include <gsl/gsl_blas.h>
+
+#define COEF ( 8 * powf(PI, 2))
+#define CHOLESKY 1
+
+typedef struct
+{
+ int nPoints;
+ int nCoeff;
+ gsl_matrix *pM;
+ gsl_matrix *pMt;
+ gsl_matrix *pR;
+ gsl_matrix *pMtMR;
+ gsl_vector *pXk;
+ gsl_vector *pMtXk;
+ gsl_vector *pC;
+ gsl_permutation *pPerm;
+} CepstrumMatrices;
+
+void FreeDCepstrum(CepstrumMatrices *m)
+{
+ gsl_matrix_free(m->pM);
+ gsl_matrix_free(m->pMt);
+ gsl_matrix_free(m->pR);
+ gsl_matrix_free(m->pMtMR);
+ gsl_vector_free(m->pXk);
+ gsl_vector_free(m->pMtXk);
+ gsl_vector_free(m->pC);
+ gsl_permutation_free (m->pPerm);
+
+}
+
+void AllocateDCepstrum(int nPoints, int nCoeff, CepstrumMatrices *m)
+{
+ if(m->nPoints != 0 || m->nCoeff != 0)
+ FreeDCepstrum(m);
+ m->nPoints = nPoints;
+ m->nCoeff = nCoeff;
+ m->pM = gsl_matrix_alloc(nPoints, nCoeff);
+ m->pMt = gsl_matrix_alloc(nCoeff, nPoints);
+ m->pR = gsl_matrix_calloc(nCoeff, nCoeff);
+ m->pMtMR = gsl_matrix_alloc(nCoeff, nCoeff);
+ m->pXk = gsl_vector_alloc(nPoints);
+ m->pMtXk = gsl_vector_alloc(nCoeff);
+ m->pC = gsl_vector_alloc(nCoeff);
+ m->pPerm = gsl_permutation_alloc (nCoeff);
+}
+
+/*! \brief Discrete Cepstrum Transform
+ *
+ * method for computing cepstrum aenalysis from a discrete
+ * set of partial peaks (frequency and amplitude)
+ *
+ * This implementation is owed to the help of Jordi Janer (thanks!) from the MTG,
+ * along with the following paper:
+ * "Regularization Techniques for Discrete Cepstrum Estimation"
+ * Olivier Cappe and Eric Moulines, IEEE Signal Processing Letters, Vol. 3
+ * No.4, April 1996
+ *
+ * \todo add anchor point add at frequency = 0 with the same magnitude as the first
+ * peak in pMag. This does not change the size of the cepstrum, only helps to smoothen it
+ * at the very beginning.
+ *
+ * \param sizeCepstrum order+1 of the discrete cepstrum
+ * \param pCepstrum pointer to output array of cepstrum coefficients
+ * \param sizeFreq number of partials peaks (the size of pFreq should be the same as pMag
+ * \param pFreq pointer to partial peak frequencies (hertz)
+ * \param pMag pointer to partial peak magnitudes (linear)
+ * \param fLambda regularization factor
+ * \param iMaxFreq maximum frequency of cepstrum
+ */
+void sms_dCepstrum( int sizeCepstrum, sfloat *pCepstrum, int sizeFreq, sfloat *pFreq, sfloat *pMag,
+ sfloat fLambda, int iMaxFreq)
+{
+ int i, k;
+ sfloat factor;
+ sfloat fNorm = PI / (sfloat)iMaxFreq; /* value to normalize frequencies to 0:0.5 */
+ //static sizeCepstrumStatic
+ static CepstrumMatrices m;
+ //printf("nPoints: %d, nCoeff: %d \n", m.nPoints, m.nCoeff);
+ if(m.nPoints != sizeCepstrum || m.nCoeff != sizeFreq)
+ AllocateDCepstrum(sizeFreq, sizeCepstrum, &m);
+ int s; /* signum: "(-1)^n, where n is the number of interchanges in the permutation." */
+ /* compute matrix M (eq. 4)*/
+ for (i=0; i<sizeFreq; i++)
+ {
+ gsl_matrix_set (m.pM, i, 0, 1.); // first colum is all 1
+ for (k=1; k <sizeCepstrum; k++)
+ gsl_matrix_set (m.pM, i, k , 2.*sms_sine(PI_2 + fNorm * k * pFreq[i]) );
+ }
+
+ /* compute transpose of M */
+ gsl_matrix_transpose_memcpy (m.pMt, m.pM);
+
+ /* compute R diagonal matrix (for eq. 7)*/
+ factor = COEF * (fLambda / (1.-fLambda)); /* \todo why is this divided like this again? */
+ for (k=0; k<sizeCepstrum; k++)
+ gsl_matrix_set(m.pR, k, k, factor * powf((sfloat) k,2.));
+
+ /* MtM = Mt * M, later will add R */
+ gsl_blas_dgemm (CblasNoTrans, CblasNoTrans, 1., m.pMt, m.pM, 0.0, m.pMtMR);
+ /* add R to make MtMR */
+ gsl_matrix_add (m.pMtMR, m.pR);
+
+ /* set pMag in X and multiply with Mt to get pMtXk */
+ for(k = 0; k <sizeFreq; k++)
+ gsl_vector_set(m.pXk, k, log(pMag[k]));
+ gsl_blas_dgemv (CblasNoTrans, 1., m.pMt, m.pXk, 0., m.pMtXk);
+
+ /* solve x (the cepstrum) in Ax = b, where A=MtMR and b=pMtXk */
+
+ /* ==== the Cholesky Decomposition way ==== */
+ /* MtM is 'symmetric and positive definite?' */
+ //gsl_linalg_cholesky_decomp (m.pMtMR);
+ //gsl_linalg_cholesky_solve (m.pMtMR, m.pMtXk, m.pC);
+
+ /* ==== the LU decomposition way ==== */
+ gsl_linalg_LU_decomp (m.pMtMR, m.pPerm, &s);
+ gsl_linalg_LU_solve (m.pMtMR, m.pPerm, m.pMtXk, m.pC);
+
+
+ /* copy pC to pCepstrum */
+ for(i = 0; i < sizeCepstrum; i++)
+ pCepstrum[i] = gsl_vector_get (m.pC, i);
+}
+
+/*! \brief Spectrum Envelope from Cepstrum
+ *
+ * from a set of cepstrum coefficients, compute the spectrum envelope
+ *
+ * \param sizeCepstrum order + 1 of the cepstrum
+ * \param pCepstrum pointer to array of cepstrum coefficients
+ * \param sizeEnv size of spectrum envelope (max frequency in bins) \todo does this have to be a pow2
+ * \param pEnv pointer to output spectrum envelope (real part only)
+ */
+void sms_dCepstrumEnvelope(int sizeCepstrum, sfloat *pCepstrum, int sizeEnv, sfloat *pEnv)
+{
+
+ static sfloat *pFftBuffer;
+ static int sizeFftArray = 0;
+ int sizeFft = sizeEnv << 1;
+ int i;
+ if(sizeFftArray != sizeFft)
+ {
+ if(sizeFftArray != 0) free(pFftBuffer);
+ sizeFftArray = sms_power2(sizeFft);
+ if(sizeFftArray != sizeFft)
+ {
+ sms_error("bad fft size, incremented to power of 2");
+ }
+ if ((pFftBuffer = (sfloat *) malloc(sizeFftArray * sizeof(sfloat))) == NULL)
+ {
+ sms_error("could not allocate memory for fft array");
+ return;
+ }
+ }
+ memset(pFftBuffer, 0, sizeFftArray * sizeof(sfloat));
+
+ pFftBuffer[0] = pCepstrum[0] * 0.5;
+ for (i = 1; i < sizeCepstrum-1; i++)
+ pFftBuffer[i] = pCepstrum[i];
+
+
+ sms_fft(sizeFftArray, pFftBuffer);
+
+ for (i = 0; i < sizeEnv; i++)
+ pEnv[i] = powf(EXP, 2. * pFftBuffer[i*2]);
+}
+
+/*! \brief main function for computing spectral envelope from sinusoidal peaks
+ *
+ * Magnitudes should already be in linear for this function.
+ * If pSmsData->iEnvelope == SMS_ENV_CEP, will return cepstrum coefficeints
+ * If pSmsData->iEnvelope == SMS_ENV_FBINS, will return linear magnitude spectrum
+ *
+ * \param pSmsData pointer to SMS_Data structure with all the arrays necessary
+ * \param pSpecEnvParams pointer to a structure of parameters for spectral enveloping
+ */
+void sms_spectralEnvelope( SMS_Data *pSmsData, SMS_SEnvParams *pSpecEnvParams)
+{
+ int i, k;
+ int sizeCepstrum = pSpecEnvParams->iOrder+1;
+ //int nPeaks = 0;
+ static sfloat pFreqBuff[1000], pMagBuff[1000];
+
+ /* \todo see if this memset is even necessary, once working */
+ //memset(pSmsData->pSpecEnv, 0, pSpecEnvParams->nCoeff * sizeof(sfloat));
+
+ /* try to store cepstrum coefficients in pSmsData->nEnvCoeff always.
+ if cepstrum is what is wanted, memset the rest. otherwise, hand this array 2x to dCepstrumEnvelope */
+ if(pSpecEnvParams->iOrder + 1> pSmsData->nEnvCoeff)
+ {
+ sms_error("cepstrum order is larger than the size of the spectral envelope");
+ return;
+ }
+
+ /* find out how many tracks were actually found... many are zero
+ \todo is this necessary? */
+ for(i = 0, k=0; i < pSmsData->nTracks; i++)
+ {
+ if(pSmsData->pFSinFreq[i] > 0.00001)
+ {
+ if(pSpecEnvParams->iAnchor != 0)
+ {
+ if(k == 0) /* add anchor at beginning */
+
+ {
+ pFreqBuff[k] = 0.0;
+ pMagBuff[k] = pSmsData->pFSinAmp[i];
+ k++;
+ }
+ }
+ pFreqBuff[k] = pSmsData->pFSinFreq[i];
+ pMagBuff[k] = pSmsData->pFSinAmp[i];
+ k++;
+ }
+ }
+ /* \todo see if adding an anchor at the max freq helps */
+
+
+ if(k < 1) // how few can this be? try out a few in python
+ return;
+ sms_dCepstrum(sizeCepstrum, pSmsData->pSpecEnv, k, pFreqBuff, pMagBuff,
+ pSpecEnvParams->fLambda, pSpecEnvParams->iMaxFreq);
+
+ if(pSpecEnvParams->iType == SMS_ENV_FBINS)
+ {
+ sms_dCepstrumEnvelope(sizeCepstrum, pSmsData->pSpecEnv,
+ pSpecEnvParams->nCoeff, pSmsData->pSpecEnv);
+ }
+}