mirror of
				https://github.com/RetroDECK/Duckstation.git
				synced 2025-04-10 19:15:14 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			278 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			278 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| ////////////////////////////////////////////////////////////////////////////////
 | |
| ///
 | |
| /// Peak detection routine. 
 | |
| ///
 | |
| /// The routine detects highest value on an array of values and calculates the 
 | |
| /// precise peak location as a mass-center of the 'hump' around the peak value.
 | |
| ///
 | |
| /// Author        : Copyright (c) Olli Parviainen
 | |
| /// Author e-mail : oparviai 'at' iki.fi
 | |
| /// SoundTouch WWW: http://www.surina.net/soundtouch
 | |
| ///
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| // License :
 | |
| //
 | |
| //  SoundTouch audio processing library
 | |
| //  Copyright (c) Olli Parviainen
 | |
| //
 | |
| //  This library is free software; you can redistribute it and/or
 | |
| //  modify it under the terms of the GNU Lesser General Public
 | |
| //  License as published by the Free Software Foundation; either
 | |
| //  version 2.1 of the License, or (at your option) any later version.
 | |
| //
 | |
| //  This library 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
 | |
| //  Lesser General Public License for more details.
 | |
| //
 | |
| //  You should have received a copy of the GNU Lesser General Public
 | |
| //  License along with this library; if not, write to the Free Software
 | |
| //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | |
| //
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| #include <math.h>
 | |
| #include <assert.h>
 | |
| 
 | |
| #include "PeakFinder.h"
 | |
| 
 | |
| using namespace soundtouch;
 | |
| 
 | |
| #define max(x, y) (((x) > (y)) ? (x) : (y))
 | |
| 
 | |
| 
 | |
| PeakFinder::PeakFinder()
 | |
| {
 | |
|     minPos = maxPos = 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| // Finds real 'top' of a peak hump from neighnourhood of the given 'peakpos'.
 | |
| int PeakFinder::findTop(const float *data, int peakpos) const
 | |
| {
 | |
|     int i;
 | |
|     int start, end;
 | |
|     float refvalue;
 | |
| 
 | |
|     refvalue = data[peakpos];
 | |
| 
 | |
|     // seek within ±10 points
 | |
|     start = peakpos - 10;
 | |
|     if (start < minPos) start = minPos;
 | |
|     end = peakpos + 10;
 | |
|     if (end > maxPos) end = maxPos;
 | |
| 
 | |
|     for (i = start; i <= end; i ++)
 | |
|     {
 | |
|         if (data[i] > refvalue)
 | |
|         {
 | |
|             peakpos = i;
 | |
|             refvalue = data[i];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // failure if max value is at edges of seek range => it's not peak, it's at slope.
 | |
|     if ((peakpos == start) || (peakpos == end)) return 0;
 | |
| 
 | |
|     return peakpos;
 | |
| }
 | |
| 
 | |
| 
 | |
| // Finds 'ground level' of a peak hump by starting from 'peakpos' and proceeding
 | |
| // to direction defined by 'direction' until next 'hump' after minimum value will 
 | |
| // begin
 | |
| int PeakFinder::findGround(const float *data, int peakpos, int direction) const
 | |
| {
 | |
|     int lowpos;
 | |
|     int pos;
 | |
|     int climb_count;
 | |
|     float refvalue;
 | |
|     float delta;
 | |
| 
 | |
|     climb_count = 0;
 | |
|     refvalue = data[peakpos];
 | |
|     lowpos = peakpos;
 | |
| 
 | |
|     pos = peakpos;
 | |
| 
 | |
|     while ((pos > minPos+1) && (pos < maxPos-1))
 | |
|     {
 | |
|         int prevpos;
 | |
| 
 | |
|         prevpos = pos;
 | |
|         pos += direction;
 | |
| 
 | |
|         // calculate derivate
 | |
|         delta = data[pos] - data[prevpos];
 | |
|         if (delta <= 0)
 | |
|         {
 | |
|             // going downhill, ok
 | |
|             if (climb_count)
 | |
|             {
 | |
|                 climb_count --;  // decrease climb count
 | |
|             }
 | |
| 
 | |
|             // check if new minimum found
 | |
|             if (data[pos] < refvalue)
 | |
|             {
 | |
|                 // new minimum found
 | |
|                 lowpos = pos;
 | |
|                 refvalue = data[pos];
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // going uphill, increase climbing counter
 | |
|             climb_count ++;
 | |
|             if (climb_count > 5) break;    // we've been climbing too long => it's next uphill => quit
 | |
|         }
 | |
|     }
 | |
|     return lowpos;
 | |
| }
 | |
| 
 | |
| 
 | |
| // Find offset where the value crosses the given level, when starting from 'peakpos' and
 | |
| // proceeds to direction defined in 'direction'
 | |
| int PeakFinder::findCrossingLevel(const float *data, float level, int peakpos, int direction) const
 | |
| {
 | |
|     float peaklevel;
 | |
|     int pos;
 | |
| 
 | |
|     peaklevel = data[peakpos];
 | |
|     assert(peaklevel >= level);
 | |
|     pos = peakpos;
 | |
|     while ((pos >= minPos) && (pos + direction < maxPos))
 | |
|     {
 | |
|         if (data[pos + direction] < level) return pos;   // crossing found
 | |
|         pos += direction;
 | |
|     }
 | |
|     return -1;  // not found
 | |
| }
 | |
| 
 | |
| 
 | |
| // Calculates the center of mass location of 'data' array items between 'firstPos' and 'lastPos'
 | |
| double PeakFinder::calcMassCenter(const float *data, int firstPos, int lastPos) const
 | |
| {
 | |
|     int i;
 | |
|     float sum;
 | |
|     float wsum;
 | |
| 
 | |
|     sum = 0;
 | |
|     wsum = 0;
 | |
|     for (i = firstPos; i <= lastPos; i ++)
 | |
|     {
 | |
|         sum += (float)i * data[i];
 | |
|         wsum += data[i];
 | |
|     }
 | |
| 
 | |
|     if (wsum < 1e-6) return 0;
 | |
|     return sum / wsum;
 | |
| }
 | |
| 
 | |
| 
 | |
| /// get exact center of peak near given position by calculating local mass of center
 | |
| double PeakFinder::getPeakCenter(const float *data, int peakpos) const
 | |
| {
 | |
|     float peakLevel;            // peak level
 | |
|     int crosspos1, crosspos2;   // position where the peak 'hump' crosses cutting level
 | |
|     float cutLevel;             // cutting value
 | |
|     float groundLevel;          // ground level of the peak
 | |
|     int gp1, gp2;               // bottom positions of the peak 'hump'
 | |
| 
 | |
|     // find ground positions.
 | |
|     gp1 = findGround(data, peakpos, -1);
 | |
|     gp2 = findGround(data, peakpos, 1);
 | |
| 
 | |
|     peakLevel = data[peakpos];
 | |
| 
 | |
|     if (gp1 == gp2) 
 | |
|     {
 | |
|         // avoid rounding errors when all are equal
 | |
|         assert(gp1 == peakpos);
 | |
|         cutLevel = groundLevel = peakLevel;
 | |
|     } else {
 | |
|         // get average of the ground levels
 | |
|         groundLevel = 0.5f * (data[gp1] + data[gp2]);
 | |
| 
 | |
|         // calculate 70%-level of the peak
 | |
|         cutLevel = 0.70f * peakLevel + 0.30f * groundLevel;
 | |
|     }
 | |
| 
 | |
|     // find mid-level crossings
 | |
|     crosspos1 = findCrossingLevel(data, cutLevel, peakpos, -1);
 | |
|     crosspos2 = findCrossingLevel(data, cutLevel, peakpos, 1);
 | |
| 
 | |
|     if ((crosspos1 < 0) || (crosspos2 < 0)) return 0;   // no crossing, no peak..
 | |
| 
 | |
|     // calculate mass center of the peak surroundings
 | |
|     return calcMassCenter(data, crosspos1, crosspos2);
 | |
| }
 | |
| 
 | |
| 
 | |
| double PeakFinder::detectPeak(const float *data, int aminPos, int amaxPos) 
 | |
| {
 | |
| 
 | |
|     int i;
 | |
|     int peakpos;                // position of peak level
 | |
|     double highPeak, peak;
 | |
| 
 | |
|     this->minPos = aminPos;
 | |
|     this->maxPos = amaxPos;
 | |
| 
 | |
|     // find absolute peak
 | |
|     peakpos = minPos;
 | |
|     peak = data[minPos];
 | |
|     for (i = minPos + 1; i < maxPos; i ++)
 | |
|     {
 | |
|         if (data[i] > peak) 
 | |
|         {
 | |
|             peak = data[i];
 | |
|             peakpos = i;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // Calculate exact location of the highest peak mass center
 | |
|     highPeak = getPeakCenter(data, peakpos);
 | |
|     peak = highPeak;
 | |
| 
 | |
|     // Now check if the highest peak were in fact harmonic of the true base beat peak 
 | |
|     // - sometimes the highest peak can be Nth harmonic of the true base peak yet 
 | |
|     // just a slightly higher than the true base
 | |
| 
 | |
|     for (i = 1; i < 3; i ++)
 | |
|     {
 | |
|         double peaktmp, harmonic;
 | |
|         int i1,i2;
 | |
| 
 | |
|         harmonic = (double)pow(2.0, i);
 | |
|         peakpos = (int)(highPeak / harmonic + 0.5f);
 | |
|         if (peakpos < minPos) break;
 | |
|         peakpos = findTop(data, peakpos);   // seek true local maximum index
 | |
|         if (peakpos == 0) continue;         // no local max here
 | |
| 
 | |
|         // calculate mass-center of possible harmonic peak
 | |
|         peaktmp = getPeakCenter(data, peakpos);
 | |
| 
 | |
|         // accept harmonic peak if 
 | |
|         // (a) it is found
 | |
|         // (b) is within ±4% of the expected harmonic interval
 | |
|         // (c) has at least half x-corr value of the max. peak
 | |
| 
 | |
|         double diff = harmonic * peaktmp / highPeak;
 | |
|         if ((diff < 0.96) || (diff > 1.04)) continue;   // peak too afar from expected
 | |
| 
 | |
|         // now compare to highest detected peak
 | |
|         i1 = (int)(highPeak + 0.5);
 | |
|         i2 = (int)(peaktmp + 0.5);
 | |
|         if (data[i2] >= 0.4*data[i1])
 | |
|         {
 | |
|             // The harmonic is at least half as high primary peak,
 | |
|             // thus use the harmonic peak instead
 | |
|             peak = peaktmp;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return peak;
 | |
| }
 | 
