summaryrefslogtreecommitdiff
path: root/src/sms/harmDetection.c
diff options
context:
space:
mode:
authorJohn Glover <glover.john@gmail.com>2011-06-24 18:17:23 +0100
committerJohn Glover <glover.john@gmail.com>2011-06-24 18:17:23 +0100
commit416bd737074a287ea47106c73ea6bcfde40a75a8 (patch)
tree74562303d4f4f2f2e010f7e13cba41dc4852b50c /src/sms/harmDetection.c
parentd26519464dcbf8c3682348167c29454961facefe (diff)
downloadsimpl-416bd737074a287ea47106c73ea6bcfde40a75a8.tar.gz
simpl-416bd737074a287ea47106c73ea6bcfde40a75a8.tar.bz2
simpl-416bd737074a287ea47106c73ea6bcfde40a75a8.zip
Change to using distutils.
Currently only builds the simplsndobj module
Diffstat (limited to 'src/sms/harmDetection.c')
-rw-r--r--src/sms/harmDetection.c390
1 files changed, 390 insertions, 0 deletions
diff --git a/src/sms/harmDetection.c b/src/sms/harmDetection.c
new file mode 100644
index 0000000..bf99729
--- /dev/null
+++ b/src/sms/harmDetection.c
@@ -0,0 +1,390 @@
+/*
+ * 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 harmDetection.c
+ * \brief Detection of a given harmonic
+ */
+
+#include "sms.h"
+
+#define N_FUND_HARM 6 /*!< number of harmonics to use for fundamental detection */
+#define N_HARM_PEAKS 4 /*!< number of peaks to check as possible ref harmonics */
+#define FREQ_DEV_THRES .07 /*!< threshold for deviation from perfect harmonics */
+#define MAG_PERC_THRES .6 /*!< threshold for magnitude of harmonics
+ with respect to the total magnitude */
+#define HARM_RATIO_THRES .8 /*!< threshold for percentage of harmonics found */
+
+/*! \brief get closest peak to a given harmonic of the possible fundamental
+ *
+ * \param iPeakCandidate peak number of possible fundamental
+ * \param nHarm number of harmonic
+ * \param pSpectralPeaks pointer to all the peaks
+ * \param pICurrentPeak pointer to the last peak taken
+ * \param iRefHarmonic reference harmonic number
+ * \return the number of the closest peak or -1 if not found
+ */
+static int GetClosestPeak(int iPeakCandidate, int nHarm, SMS_Peak *pSpectralPeaks,
+ int *pICurrentPeak, int iRefHarmonic, int maxPeaks)
+{
+ int iBestPeak = *pICurrentPeak + 1;
+ int iNextPeak = iBestPeak + 1;
+
+ if((iBestPeak >= maxPeaks) || (iNextPeak >= maxPeaks))
+ return -1;
+
+ sfloat fBestPeakFreq = pSpectralPeaks[iBestPeak].fFreq,
+ fHarmFreq = (1 + nHarm) * pSpectralPeaks[iPeakCandidate].fFreq / iRefHarmonic,
+ fMinDistance = fabs(fHarmFreq - fBestPeakFreq),
+ fMaxPeakDev = .5 * fHarmFreq / (nHarm + 1),
+ fDistance = 0.0;
+
+ fDistance = fabs(fHarmFreq - pSpectralPeaks[iNextPeak].fFreq);
+ while((fDistance < fMinDistance) && (iNextPeak < maxPeaks - 1))
+ {
+ iBestPeak = iNextPeak;
+ fMinDistance = fDistance;
+ iNextPeak++;
+ fDistance = fabs(fHarmFreq - pSpectralPeaks[iNextPeak].fFreq);
+ }
+
+ /* make sure the chosen peak is good */
+ fBestPeakFreq = pSpectralPeaks[iBestPeak].fFreq;
+
+ /* if best peak is not in the range */
+ if(fabs(fBestPeakFreq - fHarmFreq) > fMaxPeakDev)
+ return -1;
+
+ *pICurrentPeak = iBestPeak;
+ return iBestPeak;
+}
+
+/*! \brief checks if peak is substantial
+ *
+ * check if peak is larger enough to be considered a fundamental
+ * without any further testing or too small to be considered
+ *
+
+ * \param fRefHarmMag magnitude of possible fundamental
+ * \param pSpectralPeaks all the peaks
+ * \param nCand number of existing candidates
+ * \param fRefHarmMagDiffFromMax value to judge the peak based on the difference of its magnitude compared to the reference
+ * \return 1 if big peak, -1 if too small , otherwise return 0
+ */
+static int ComparePeak(sfloat fRefHarmMag, SMS_Peak *pSpectralPeaks, int nCand,
+ sfloat fRefHarmMagDiffFromMax, int maxPeaks)
+{
+ int iPeak;
+ sfloat fMag = 0;
+
+ /* if peak is very large take it as possible fundamental */
+ if(nCand == 0 && fRefHarmMag > 80.)
+ return 1;
+
+ /* compare the peak with the first N_FUND_HARM peaks */
+ /* if too small forget it */
+ for(iPeak = 0; (iPeak < N_FUND_HARM) && (iPeak < maxPeaks); iPeak++)
+ {
+ if(pSpectralPeaks[iPeak].fMag > 0 &&
+ fRefHarmMag - pSpectralPeaks[iPeak].fMag < - fRefHarmMagDiffFromMax)
+ return -1;
+ }
+
+ /* if it is much bigger than rest take it */
+ for(iPeak = 0; (iPeak < N_FUND_HARM) && (iPeak < maxPeaks); iPeak++)
+ {
+ fMag = pSpectralPeaks[iPeak].fMag;
+ if(fMag <= 0 ||
+ ((fMag != fRefHarmMag) && (nCand > 0) && (fRefHarmMag - fMag < 30.0)) ||
+ ((nCand == 0) && (fRefHarmMag - fMag < 15.0)))
+ return 0;
+ }
+ return 1;
+}
+
+
+/*! \brief check if the current peak is a harmonic of one of the candidates
+ *
+ * \param fFundFreq frequency of peak to be tested
+ * \param pCHarmonic all candidates accepted
+ * \param nCand location of las candidate
+ * \return 1 if it is a harmonic, 0 if it is not
+ */
+static int CheckIfHarmonic(sfloat fFundFreq, SMS_HarmCandidate *pCHarmonic, int nCand)
+{
+ int iPeak;
+
+ /* go through all the candidates checking if they are fundamentals
+ * of the peak to be considered */
+ for(iPeak = 0; iPeak < nCand; iPeak++)
+ {
+ if(fabs(floor((double)(fFundFreq / pCHarmonic[iPeak].fFreq) + .5) -
+ (fFundFreq / pCHarmonic[iPeak].fFreq)) <= .1)
+ return 1;
+ }
+ return 0;
+}
+
+
+/*! \brief consider a peak as a possible candidate and give it a weight value,
+ *
+ * \param iPeak iPeak number to be considered
+ * \param pSpectralPeaks all the peaks
+ * \param pCHarmonic all the candidates
+ * \param nCand candidate number that is to be filled
+ * \param pPeakParams analysis parameters
+ * \param fRefFundamental previous fundamental
+ * \return -1 if not good enough for a candidate, return 0 if reached
+ * the top frequency boundary, return -2 if stop checking because it
+ * found a really good one, return 1 if the peak is a good candidate
+ */
+
+static int GoodCandidate(int iPeak, int maxPeaks, SMS_Peak *pSpectralPeaks,
+ SMS_HarmCandidate *pCHarmonic, int nCand, int soundType, sfloat fRefFundamental,
+ sfloat minRefHarmMag, sfloat refHarmMagDiffFromMax, sfloat refHarmonic)
+{
+ sfloat fHarmFreq = 0.0,
+ fRefHarmFreq = 0.0,
+ fRefHarmMag = 0.0,
+ fTotalMag = 0.0,
+ fTotalDev = 0.0,
+ fTotalMaxMag = 0.0,
+ fAvgMag = 0.0,
+ fAvgDev = 0.0,
+ fHarmRatio = 0.0;
+ int iHarm = 0,
+ iChosenPeak = 0,
+ iPeakComp = 0,
+ iCurrentPeak = 0,
+ nGoodHarm = 0,
+ i = 0;
+
+ fRefHarmFreq = fHarmFreq = pSpectralPeaks[iPeak].fFreq;
+
+ fTotalDev = 0;
+ fRefHarmMag = pSpectralPeaks[iPeak].fMag;
+ fTotalMag = fRefHarmMag;
+
+ /* check if magnitude is big enough */
+ /*! \bug sfloat comparison to 0 */
+ if(((fRefFundamental > 0) && (fRefHarmMag < minRefHarmMag - 10)) ||
+ ((fRefFundamental <= 0) && (fRefHarmMag < minRefHarmMag)))
+ return -1;
+
+ /* check that it is not a harmonic of a previous candidate */
+ if(nCand > 0 &&
+ CheckIfHarmonic(fRefHarmFreq / refHarmonic, pCHarmonic, nCand))
+ return -1;
+
+ /* check if it is very big or very small */
+ iPeakComp = ComparePeak(fRefHarmMag, pSpectralPeaks, nCand, refHarmMagDiffFromMax, maxPeaks);
+
+ /* too small */
+ if(iPeakComp == -1)
+ return -1;
+ /* very big */
+ else if(iPeakComp == 1)
+ {
+ pCHarmonic[nCand].fFreq = fRefHarmFreq;
+ pCHarmonic[nCand].fMag = fRefHarmMag;
+ pCHarmonic[nCand].fMagPerc = 1;
+ pCHarmonic[nCand].fFreqDev = 0;
+ pCHarmonic[nCand].fHarmRatio = 1;
+ return -2;
+ }
+
+ /* get a weight on the peak by comparing its harmonic series */
+ /* with the existing peaks */
+ if(soundType != SMS_SOUND_TYPE_NOTE)
+ {
+ fHarmFreq = fRefHarmFreq;
+ iCurrentPeak = iPeak;
+ nGoodHarm = 0;
+ for(iHarm = refHarmonic; (iHarm < N_FUND_HARM) && (iHarm < maxPeaks); iHarm++)
+ {
+ fHarmFreq += fRefHarmFreq / refHarmonic;
+ iChosenPeak = GetClosestPeak(iPeak, iHarm, pSpectralPeaks,
+ &iCurrentPeak, refHarmonic,
+ maxPeaks);
+ if(iChosenPeak > 0)
+ {
+ fTotalDev += fabs(fHarmFreq - pSpectralPeaks[iChosenPeak].fFreq) /
+ fHarmFreq;
+ fTotalMag += pSpectralPeaks[iChosenPeak].fMag;
+ nGoodHarm++;
+ }
+ }
+
+ for(i = 0; i <= iCurrentPeak; i++)
+ fTotalMaxMag += pSpectralPeaks[i].fMag;
+
+ fAvgDev = fTotalDev / (iHarm + 1);
+ fAvgMag = fTotalMag / fTotalMaxMag;
+ fHarmRatio = (sfloat)nGoodHarm / (N_FUND_HARM - 1);
+
+ if(fRefFundamental > 0)
+ {
+ if(fAvgDev > FREQ_DEV_THRES || fAvgMag < MAG_PERC_THRES - .1 ||
+ fHarmRatio < HARM_RATIO_THRES - .1)
+ return -1;
+ }
+ else
+ {
+ if(fAvgDev > FREQ_DEV_THRES || fAvgMag < MAG_PERC_THRES ||
+ fHarmRatio < HARM_RATIO_THRES)
+ return -1;
+ }
+ }
+
+ pCHarmonic[nCand].fFreq = fRefHarmFreq;
+ pCHarmonic[nCand].fMag = fRefHarmMag;
+ pCHarmonic[nCand].fMagPerc = fAvgMag;
+ pCHarmonic[nCand].fFreqDev = fAvgDev;
+ pCHarmonic[nCand].fHarmRatio = fHarmRatio;
+
+ return 1;
+}
+
+/*! \brief choose the best fundamental out of all the candidates
+ *
+ * \param pCHarmonic array of candidates
+ * \param iRefHarmonic reference harmonic number
+ * \param nGoodPeaks number of candiates
+ * \param fPrevFund reference fundamental
+ * \return the integer number of the best candidate
+ */
+static int GetBestCandidate(SMS_HarmCandidate *pCHarmonic,
+ int iRefHarmonic, int nGoodPeaks, sfloat fPrevFund)
+{
+ int iBestCandidate = 0, iPeak;
+ sfloat fBestFreq, fHarmFreq, fDev;
+
+ /* if a fundamental existed in previous frame take the closest candidate */
+ if(fPrevFund > 0)
+ {
+ for(iPeak = 1; iPeak < nGoodPeaks; iPeak++)
+ {
+ if(fabs(fPrevFund - pCHarmonic[iPeak].fFreq / iRefHarmonic) <
+ fabs(fPrevFund - pCHarmonic[iBestCandidate].fFreq / iRefHarmonic))
+ iBestCandidate = iPeak;
+ }
+ }
+ else
+ {
+ /* try to find the best candidate */
+ for(iPeak = 1; iPeak < nGoodPeaks; iPeak++)
+ {
+ fBestFreq = pCHarmonic[iBestCandidate].fFreq / iRefHarmonic;
+ fHarmFreq = fBestFreq * floor(.5 +
+ (pCHarmonic[iPeak].fFreq / iRefHarmonic) /
+ fBestFreq);
+ fDev = fabs(fHarmFreq - (pCHarmonic[iPeak].fFreq / iRefHarmonic)) / fHarmFreq;
+
+ /* if candidate is far from harmonic from best candidate and
+ * bigger, take it */
+ if(fDev > .2 &&
+ pCHarmonic[iPeak].fMag > pCHarmonic[iBestCandidate].fMag)
+ iBestCandidate = iPeak;
+ /* if frequency deviation is much smaller, take it */
+ else if(pCHarmonic[iPeak].fFreqDev < .2 * pCHarmonic[iBestCandidate].fFreqDev)
+ iBestCandidate = iPeak;
+ /* if freq. deviation is smaller and bigger amplitude, take it */
+ else if(pCHarmonic[iPeak].fFreqDev < pCHarmonic[iBestCandidate].fFreqDev &&
+ pCHarmonic[iPeak].fMagPerc > pCHarmonic[iBestCandidate].fMagPerc &&
+ pCHarmonic[iPeak].fMag > pCHarmonic[iBestCandidate].fMag)
+ iBestCandidate = iPeak;
+ }
+ }
+ return iBestCandidate;
+}
+
+/*! \brief main harmonic detection function
+ *
+ * find a given harmonic peak from a set of spectral peaks,
+ * put the frequency of the fundamental in the current frame
+ *
+ * \param pFrame pointer to current frame
+ * \param fRefFundamental frequency of previous frame
+ * \param pPeakParams pointer to analysis parameters
+ * \todo is it possible to use pSpectralPeaks instead of SMS_AnalFrame?
+ * \todo move pCHarmonic array to SMS_AnalFrame structure
+ - this will allow for analysis of effectiveness from outside this file
+ * This really should only be for sms_analyzeFrame
+ */
+sfloat sms_harmDetection(int numPeaks, SMS_Peak* spectralPeaks, sfloat refFundamental,
+ sfloat refHarmonic, sfloat lowestFreq, sfloat highestFreq,
+ int soundType, sfloat minRefHarmMag, sfloat refHarmMagDiffFromMax)
+{
+ int iPeak = -1, nGoodPeaks = 0, iCandidate, iBestCandidate;
+ sfloat peakFreq=0;
+ SMS_HarmCandidate pCHarmonic[N_HARM_PEAKS];
+
+ /* find all possible candidates to use as harmonic reference */
+ lowestFreq = lowestFreq * refHarmonic;
+ highestFreq = highestFreq * refHarmonic;
+
+ while((peakFreq < highestFreq) && (iPeak < numPeaks - 1))
+ {
+ iPeak++;
+ peakFreq = spectralPeaks[iPeak].fFreq;
+ if(peakFreq > highestFreq)
+ break;
+
+ /* no more peaks */
+ if(spectralPeaks[iPeak].fMag <= 0) /*!< \bug sfloat comparison to zero */
+ break;
+
+ /* peak too low */
+ if(peakFreq < lowestFreq)
+ continue;
+
+ /* if previous fundamental look only around it */
+ if(refFundamental > 0 &&
+ fabs(peakFreq - (refHarmonic * refFundamental)) / refFundamental > .5)
+ continue;
+
+ iCandidate = GoodCandidate(iPeak, numPeaks, spectralPeaks, pCHarmonic,
+ nGoodPeaks, soundType, refFundamental,
+ minRefHarmMag, refHarmMagDiffFromMax, refHarmonic);
+
+ /* good candiate found */
+ if(iCandidate == 1)
+ nGoodPeaks++;
+
+ /* a perfect candiate found */
+ else if(iCandidate == -2)
+ {
+ nGoodPeaks++;
+ break;
+ }
+ }
+
+ /* if no candidate for fundamental, continue */
+ if(nGoodPeaks == 0)
+ return -1;
+ /* if only 1 candidate for fundamental take it */
+ else if(nGoodPeaks == 1)
+ return pCHarmonic[0].fFreq / refHarmonic;
+ /* if more than one candidate choose the best one */
+ else
+ {
+ iBestCandidate = GetBestCandidate(pCHarmonic, refHarmonic, nGoodPeaks, refFundamental);
+ return pCHarmonic[iBestCandidate].fFreq / refHarmonic;
+ }
+}