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