You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

375 lines
12KB

  1. #include <assert.h>
  2. #include "asserts.h"
  3. #include "Analyzer.h"
  4. #include "AudioMath.h"
  5. #include "FFT.h"
  6. #include "FFTData.h"
  7. #include "SinOscillator.h"
  8. #include "AudioMath.h"
  9. int Analyzer::getMax(const FFTDataCpx& data)
  10. {
  11. return getMaxExcluding(data, std::set<int>());
  12. }
  13. int Analyzer::getMaxExcluding(const FFTDataCpx& data, int exclusion)
  14. {
  15. std::set<int> exclusions;
  16. exclusions.insert(exclusion);
  17. return getMaxExcluding(data, exclusions);
  18. }
  19. int Analyzer::getMaxExcluding(const FFTDataCpx& data, std::set<int> exclusions)
  20. {
  21. int maxBin = -1;
  22. float maxMag = 0;
  23. for (int i = 0; i < data.size(); ++i) {
  24. if (exclusions.find(i) == exclusions.end()) {
  25. const float mag = std::abs(data.get(i));
  26. if (mag > maxMag) {
  27. maxMag = mag;
  28. maxBin = i;
  29. }
  30. }
  31. }
  32. return maxBin;
  33. }
  34. static int getMaxExcluding(const FFTDataCpx&, int excludeBin);
  35. float Analyzer::getSlope(const FFTDataCpx& response, float fTest, float sampleRate)
  36. {
  37. const int bin1 = FFT::freqToBin(fTest, sampleRate, response.size());
  38. const int bin2 = bin1 * 4; // two octaves
  39. assert(bin2 < response.size());
  40. const float mag1 = response.getAbs(bin1);
  41. const float mag2 = response.getAbs(bin2);
  42. return float(AudioMath::db(mag2) - AudioMath::db(mag1)) / 2;
  43. }
  44. std::tuple<int, int, int> Analyzer::getMaxAndShoulders(const FFTDataCpx& data, float atten)
  45. {
  46. assert(atten < 0);
  47. int maxBin = getMax(data);
  48. int iMax = data.size() / 2;
  49. assert(maxBin >= 0);
  50. const double dbShoulder = atten + AudioMath::db(std::abs(data.get(maxBin)));
  51. int i;
  52. int iShoulderLow = -1;
  53. int iShoulderHigh = -1;
  54. bool done;
  55. for (done = false, i = maxBin; !done; ) {
  56. const double db = AudioMath::db(std::abs(data.get(i)));
  57. if (i >= iMax) {
  58. done = true;
  59. } else if (db <= dbShoulder) {
  60. iShoulderHigh = i;
  61. done = true;
  62. } else {
  63. i++;
  64. }
  65. }
  66. for (done = false, i = maxBin; !done; ) {
  67. const double db = AudioMath::db(std::abs(data.get(i)));
  68. if (db <= dbShoulder) {
  69. iShoulderLow = i;
  70. done = true;
  71. } else if (i <= 0) {
  72. done = true;
  73. } else {
  74. i--;
  75. }
  76. }
  77. // printf("out of loop, imax=%d, shoulders=%d,%d\n", maxBin, iShoulderLow, iShoulderHigh);
  78. return std::make_tuple(iShoulderLow, maxBin, iShoulderHigh);
  79. }
  80. std::tuple<double, double, double> Analyzer::getMaxAndShouldersFreq(const FFTDataCpx& data, float atten, float sampleRate)
  81. {
  82. auto stats = getMaxAndShoulders(data, atten);
  83. return std::make_tuple(FFT::bin2Freq(std::get<0>(stats), sampleRate, data.size()),
  84. FFT::bin2Freq(std::get<1>(stats), sampleRate, data.size()),
  85. FFT::bin2Freq(std::get<2>(stats), sampleRate, data.size())
  86. );
  87. }
  88. // TODO: pass in cutoff
  89. std::vector<Analyzer::FPoint> Analyzer::getFeatures(const FFTDataCpx& data, float sensitivityDb, float sampleRate, float dbMinCutoff)
  90. {
  91. // TODO: pass this in
  92. // const float dbMinCutoff = -100;
  93. assert(sensitivityDb > 0);
  94. std::vector<FPoint> ret;
  95. float lastDb = 10000000000;
  96. // only look at the below nyquist stuff
  97. for (int i = 0; i < data.size() / 2; ++i) {
  98. const float db = (float) AudioMath::db(std::abs(data.get(i)));
  99. if ((std::abs(db - lastDb) >= sensitivityDb) && (db > dbMinCutoff)) {
  100. double freq = FFT::bin2Freq(i, sampleRate, data.size());
  101. FPoint p(float(freq), db);
  102. // printf("feature at bin %d, db=%f raw val=%f\n", i, db, std::abs(data.get(i)));
  103. ret.push_back(p);
  104. lastDb = db;
  105. }
  106. }
  107. return ret;
  108. }
  109. std::vector<Analyzer::FPoint> Analyzer::getPeaks(const FFTDataCpx& data, float sampleRate, float minDb)
  110. {
  111. std::vector<FPoint> ret;
  112. // only look at the below nyquist stuff
  113. for (int i = 0; i < data.size() / 2; ++i) {
  114. const double mag = std::abs(data.get(i));
  115. const double db = AudioMath::db(mag);
  116. bool isPeak = false;
  117. if (i < 2 || i >(data.size() / 2) - 2) {
  118. isPeak = true;
  119. } else {
  120. const double magBelow = std::abs(data.get(i - 1));
  121. const double magAbove = std::abs(data.get(i + 1));
  122. if (mag <= magBelow || mag <= magAbove) {
  123. isPeak = false;
  124. } else {
  125. #if 1
  126. double average = 0;
  127. for (int j = 0; j < 5; ++j) {
  128. average += std::abs(data.get(i + j - 2));
  129. }
  130. average -= mag; // subtract out our contribution
  131. average /= 4.0;
  132. double a = std::abs(data.get(i - 2));
  133. double b = std::abs(data.get(i - 1));
  134. double c = std::abs(data.get(i - 0));
  135. double d = std::abs(data.get(i + 1));
  136. double e = std::abs(data.get(i + 2));
  137. isPeak = (mag > (average * 2));
  138. #else
  139. //this way average db
  140. double average = 0;
  141. for (int j = 0; j < 5; ++j) {
  142. average += AudioMath::db(std::abs(data.get(i + j - 2)));
  143. }
  144. isPeak = (db > (average + 3));
  145. #endif
  146. //if (isPeak) printf("accepted peak at %f db, average was %f\n", db, average);
  147. }
  148. }
  149. if (db < minDb) {
  150. isPeak = false;
  151. }
  152. if (isPeak) {
  153. //if ((std::abs(db - lastDb) >= sensitivityDb) && (db > dbMinCutoff)) {
  154. double freq = FFT::bin2Freq(i, sampleRate, data.size());
  155. FPoint p(float(freq), (float) db);
  156. // printf("feature at bin %d, db=%f raw val=%f\n", i, db, std::abs(data.get(i)));
  157. ret.push_back(p);
  158. }
  159. }
  160. return ret;
  161. }
  162. void Analyzer::getAndPrintFeatures(const FFTDataCpx& data, float sensitivityDb, float sampleRate, float dbMinCutoff)
  163. {
  164. auto features = getFeatures(data, sensitivityDb, sampleRate, dbMinCutoff);
  165. printf("there are %d features\n", (int) features.size());
  166. for (int i = 0; i < (int) features.size(); ++i) {
  167. printf("feature: freq=%.2f, db=%.2f\n", features[i].freq, features[i].gainDb);
  168. }
  169. }
  170. void Analyzer::getAndPrintPeaks(const FFTDataCpx& data, float sampleRate, float minDb)
  171. {
  172. auto peaks = getPeaks(data, sampleRate, minDb);
  173. printf("there are %d peaks\n", (int) peaks.size());
  174. for (int i = 0; i < (int) peaks.size(); ++i) {
  175. printf("peak: freq=%f, db=%f\n", peaks[i].freq, peaks[i].gainDb);
  176. }
  177. }
  178. void Analyzer::getAndPrintFreqOfInterest(const FFTDataCpx& data, float sampleRate, const std::vector<double>& freqOfInterest)
  179. {
  180. for (double freq : freqOfInterest) {
  181. int bin = FFT::freqToBin((float) freq, sampleRate, data.size());
  182. if (bin > 2 && bin < data.size() - 2) {
  183. ;
  184. double a = AudioMath::db(std::abs(data.get(bin - 2)));
  185. double b = AudioMath::db(std::abs(data.get(bin - 1)));
  186. double c = AudioMath::db(std::abs(data.get(bin)));
  187. double d = AudioMath::db(std::abs(data.get(bin + 1)));
  188. double e = AudioMath::db(std::abs(data.get(bin + 2)));
  189. double db = std::max(e, std::max(
  190. std::max(a, b),
  191. std::max(c, d)));
  192. printf("freq=%.2f db=%f range:%.2f,%.2f,%.2f,%.2f,%.2f\n", freq, db,
  193. a, b, c, d, e
  194. );
  195. }
  196. }
  197. #if 0
  198. for (int i = 0; i < data.size() / 2; ++i) {
  199. double freq = FFT::bin2Freq(i, sampleRate, data.size());
  200. }
  201. #endif
  202. }
  203. void Analyzer::getFreqResponse(FFTDataCpx& out, std::function<float(float)> func)
  204. {
  205. /**
  206. * testSignal is the time domain sweep
  207. * testOutput if the time domain output of "func"
  208. * testSpecrum is the FFT of testSignal
  209. * spectrum is the FFT of testOutput
  210. */
  211. // First set up a test signal
  212. const int numSamples = out.size();
  213. // std::vector<float> testSignal(numSamples);
  214. FFTDataReal testSignal(numSamples);
  215. generateSweep(44100, testSignal.data(), numSamples, 20, 20000);
  216. // Run the test signal though func, capture output in fft real
  217. FFTDataReal testOutput(numSamples);
  218. for (int i = 0; i < out.size(); ++i) {
  219. const float y = func(testSignal.get(i));
  220. testOutput.set(i, y);
  221. }
  222. // then take the inverse fft
  223. FFTDataCpx spectrum(numSamples);
  224. FFT::forward(&spectrum, testOutput);
  225. // take the forward FFT of the test signal
  226. FFTDataCpx testSpectrum(numSamples);
  227. FFT::forward(&testSpectrum, testSignal);
  228. for (int i = 0; i < numSamples; ++i) {
  229. const cpx x = (std::abs(testSpectrum.get(i)) == 0) ? 0 :
  230. spectrum.get(i) / testSpectrum.get(i);
  231. out.set(i, x);
  232. }
  233. #if 0
  234. for (int i = 0; i < numSamples; ++i) {
  235. printf("%d, sig=%f out=%f mag(sig)=%f mag(out)=%f rsp=%f\n",
  236. i, testSignal.get(i), testOutput.get(i),
  237. std::abs(testSpectrum.get(i)),
  238. std::abs(spectrum.get(i)),
  239. std::abs(out.get(i))
  240. );
  241. }
  242. #endif
  243. }
  244. double Analyzer::hamming(int iSample, int totalSamples)
  245. {
  246. const double a0 = .53836;
  247. double theta = AudioMath::Pi * 2.0 * double(iSample) / double(totalSamples - 1);
  248. return a0 - (1.0 - a0) * std::cos(theta);
  249. }
  250. void Analyzer::getSpectrum(FFTDataCpx& out, bool useWindow, std::function<float()> func)
  251. {
  252. const int numSamples = out.size();
  253. // Run the test signal though func, capture output in fft real
  254. FFTDataReal testOutput(numSamples);
  255. for (int i = 0; i < out.size(); ++i) {
  256. const float w = useWindow ? (float) hamming(i, out.size()) : 1;
  257. const float y = float(func() * w);
  258. testOutput.set(i, y);
  259. }
  260. FFT::forward(&out, testOutput);
  261. #if 0
  262. for (int i = 0; i < numSamples; ++i) {
  263. printf("%d, sig=%f out=%f mag(sig)=%f mag(out)=%f rsp=%f\n",
  264. i, testSignal.get(i), testOutput.get(i),
  265. std::abs(testSpectrum.get(i)),
  266. std::abs(spectrum.get(i)),
  267. std::abs(out.get(i))
  268. );
  269. }
  270. #endif
  271. }
  272. void Analyzer::generateSweep(float sampleRate, float* out, int numSamples, float minFreq, float maxFreq)
  273. {
  274. assert(maxFreq > minFreq);
  275. const double minLog = std::log2(minFreq);
  276. const double maxLog = std::log2(maxFreq);
  277. const double delta = (maxLog - minLog) / numSamples;
  278. SinOscillatorParams<double> params;
  279. SinOscillatorState<double> state;
  280. double fLog = minLog;
  281. for (int i = 0; i < numSamples; ++i, fLog += delta) {
  282. const double freq = std::pow(2, fLog);
  283. assert(freq < (sampleRate / 2));
  284. SinOscillator<double, false>::setFrequency(params, freq / sampleRate);
  285. double val = SinOscillator<double, false>::run(state, params);
  286. // ::printf("out[%d] = %f f=%f\n", i, val, freq);
  287. out[i] = (float) val;
  288. }
  289. }
  290. double Analyzer::makeEvenPeriod(double desiredFreq, double sampleRate, int numSamples)
  291. {
  292. assert(desiredFreq <= (sampleRate / 2.0));
  293. assert(numSamples > 2);
  294. double desiredPeriodSamples = sampleRate / desiredFreq;
  295. double periodsPerFrame = numSamples / desiredPeriodSamples;
  296. //printf("desiredFreq = %f, desired period/ samp = %f, periods per frame = %f\n", desiredFreq, desiredPeriodSamples, periodsPerFrame);
  297. double evenPeriodsPerFrame = std::floor(periodsPerFrame);
  298. double period = (double) numSamples / evenPeriodsPerFrame;
  299. //printf("period = %f\n", period);
  300. double freq = sampleRate / period;
  301. //printf("freq = %f\n", freq);
  302. assert(freq > .0001); // don't want zero
  303. return (freq);
  304. }
  305. void Analyzer::assertSingleFreq(const FFTDataCpx& spectrum, float expectedFreq, float sampleRate)
  306. {
  307. assert(expectedFreq < (sampleRate / 2));
  308. int maxBin = Analyzer::getMax(spectrum);
  309. double maxFreq = FFT::bin2Freq(maxBin, sampleRate, spectrum.size());
  310. int nextMaxBin = Analyzer::getMaxExcluding(spectrum, maxBin);
  311. float maxPower = std::abs(spectrum.get(maxBin));
  312. float nextMaxPower = std::abs(spectrum.get(nextMaxBin));
  313. double spuriousDb = AudioMath::db(nextMaxPower / maxPower);
  314. assertClose(maxFreq, expectedFreq, 1);
  315. assertLE(spuriousDb, 70);
  316. }