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.

511 lines
14KB

  1. #include "Analyzer.h"
  2. #include "asserts.h"
  3. #include "EvenVCO.h"
  4. #include "FunVCO.h"
  5. #include "SawOscillator.h"
  6. #include "SinOscillator.h"
  7. #include "TestComposite.h"
  8. // globals for these tests
  9. static const float sampleRate = 44100;
  10. static bool expandBins = false;
  11. static bool adjustBins = true;
  12. #if 0
  13. static const int numSamples = 64 * 1024;
  14. static const double expandThresholdDb = -10;
  15. #else
  16. static const int numSamples = 64 * 1024;
  17. static const double expandThresholdDb = .01; // normally negative
  18. #endif
  19. static void testPitchQuantize()
  20. {
  21. const double sampleRate = 44100;
  22. const int numSamples = 16;
  23. const double inputFreq = 44100.0 / 4.0;
  24. double freq = Analyzer::makeEvenPeriod(inputFreq, sampleRate, numSamples);
  25. // check that quantized pitch is in the bin we expect.
  26. assertEQ(freq, FFT::bin2Freq(4, sampleRate, numSamples));
  27. // make saw osc at correct freq
  28. SinOscillatorParams<double> params;
  29. SinOscillatorState<double> state;
  30. SinOscillator<double, false>::setFrequency(params, 1.0 / 4.0);
  31. // check that spectrum has only a single freq
  32. std::function<float()> func = [&state, &params]() {
  33. return float(30 * SinOscillator<double, false>::run(state, params));
  34. };
  35. FFTDataCpx spectrum(numSamples);
  36. Analyzer::getSpectrum(spectrum, false, func);
  37. for (int i = 0; i < numSamples / 2; ++i) {
  38. const float abs = spectrum.getAbs(i);
  39. if (i == 4) {
  40. assertGE(abs, .5);
  41. } else {
  42. assertLT(abs, 0.000000001);
  43. }
  44. }
  45. }
  46. class AliasStats
  47. {
  48. public:
  49. float totalAliasDb;
  50. float totalAliasAWeighted;
  51. float maxAliasFreq;
  52. };
  53. /*
  54. Next: examine the spectrum. make sure all freq in spectrum are signal or alias
  55. */
  56. class FrequencySets
  57. {
  58. public:
  59. FrequencySets(double fundamental, double sampleRate, const FFTDataCpx& spectrum);
  60. void expandFrequencies();
  61. bool checkOverlap() const;
  62. std::set<double> harmonics;
  63. std::set<double> alias;
  64. void adjustFrequencies();
  65. void dump(const char *) const;
  66. private:
  67. static void expandFrequencies(std::set<double>&, const FFTDataCpx& spectrum);
  68. bool adjustFrequencies1();
  69. static bool adjustFreqHelper(int bin, int tryBin, std::set<double>& set, const FFTDataCpx& spectrum);
  70. const FFTDataCpx& spectrum;
  71. };
  72. inline void FrequencySets::adjustFrequencies()
  73. {
  74. int tries = 0;
  75. while (adjustFrequencies1()) {
  76. ++tries;
  77. }
  78. //printf("adjust moved %d\n", tries);
  79. }
  80. inline bool FrequencySets::adjustFreqHelper(int bin, int tryBin, std::set<double>& set, const FFTDataCpx& spectrum)
  81. {
  82. bool ret = false;
  83. if (tryBin < 0 || tryBin >= spectrum.size()) {
  84. return false;
  85. }
  86. const double db = AudioMath::db(spectrum.getAbs(bin));
  87. const double dbm1 = AudioMath::db(spectrum.getAbs(tryBin));
  88. if (dbm1 > (db + 3)) { // only adjust a bin if it's a 3db improvement
  89. const double f = FFT::bin2Freq(bin, sampleRate, spectrum.size());
  90. const double fNew = FFT::bin2Freq(tryBin, sampleRate, spectrum.size());
  91. auto x = set.erase(f);
  92. assert(x == 1);
  93. set.insert(fNew);
  94. ret = true;
  95. }
  96. return ret;
  97. }
  98. inline bool FrequencySets::adjustFrequencies1()
  99. {
  100. for (auto f : harmonics) {
  101. const int bin = FFT::freqToBin(f, sampleRate, spectrum.size());
  102. if (adjustFreqHelper(bin, bin - 1, harmonics, spectrum))
  103. return true;
  104. if (adjustFreqHelper(bin, bin + 1, harmonics, spectrum))
  105. return true;
  106. }
  107. for (auto f : alias) {
  108. const int bin = FFT::freqToBin(f, sampleRate, spectrum.size());
  109. if (adjustFreqHelper(bin, bin - 1, alias, spectrum))
  110. return true;
  111. if (adjustFreqHelper(bin, bin + 1, alias, spectrum))
  112. return true;
  113. }
  114. return false;
  115. }
  116. inline FrequencySets::FrequencySets(double fundamental, double sampleRate, const FFTDataCpx& spectrum) :
  117. spectrum(spectrum)
  118. {
  119. const double nyquist = sampleRate / 2;
  120. bool done = false;
  121. for (int i = 1; !done; ++i) {
  122. double freq = fundamental * i;
  123. if (freq < nyquist) {
  124. //harmonics.push_back(freq);
  125. harmonics.insert(freq);
  126. } else {
  127. double over = freq - nyquist;
  128. if (over < nyquist) {
  129. freq = nyquist - over;
  130. //alias.push_back(freq);
  131. alias.insert(freq);
  132. } else {
  133. done = true;
  134. }
  135. }
  136. }
  137. }
  138. inline void expandHelper(double& maxDb, bool& done, int& i, int deltaI, const FFTDataCpx& spectrum, std::set<double>& f)
  139. {
  140. if (i >= spectrum.size() || i < 0) {
  141. done = true;
  142. } else {
  143. const double db = AudioMath::db(spectrum.getAbs(i));
  144. if (db < (maxDb + expandThresholdDb)) {
  145. done = true;
  146. } else {
  147. //const double oldFreq = FFT::bin2Freq(i, sampleRate, spectrum.size());
  148. const double newFreq = FFT::bin2Freq(i, sampleRate, spectrum.size());
  149. if (newFreq < 900 && newFreq > 800)
  150. printf("inserting new freq %f db=%f m=%f\n ", newFreq, db, maxDb);
  151. maxDb = std::max(maxDb, db);
  152. f.insert(newFreq);
  153. }
  154. }
  155. i += deltaI;
  156. }
  157. inline void FrequencySets::expandFrequencies(std::set<double>& f, const FFTDataCpx& spectrum)
  158. {
  159. assert(expandBins);
  160. for (double freq : f) {
  161. if (int(freq) == 1064) {
  162. int x = 5;
  163. }
  164. const int bin = FFT::freqToBin(freq, sampleRate, spectrum.size());
  165. double maxDb = AudioMath::db(spectrum.getAbs(bin));
  166. // search upward
  167. bool done;
  168. int i;
  169. for (i = bin + 1, done = false; !done; ) {
  170. expandHelper(maxDb, done, i, 1, spectrum, f);
  171. }
  172. for (i = bin - 1, done = false; !done; ) {
  173. expandHelper(maxDb, done, i, -1, spectrum, f);
  174. }
  175. }
  176. }
  177. inline void FrequencySets::dump(const char *label) const
  178. {
  179. printf("**** %s ****\n", label);
  180. for (auto f : harmonics) {
  181. int bin = FFT::freqToBin(f, sampleRate, spectrum.size());
  182. printf("harm at %.2f db:%.2f\n", f, AudioMath::db(spectrum.getAbs(bin)));
  183. }
  184. for (auto f : alias) {
  185. int bin = FFT::freqToBin(f, sampleRate, spectrum.size());
  186. printf("alias at %.2f db:%.2f\n", f, AudioMath::db(spectrum.getAbs(bin)));
  187. }
  188. }
  189. inline void FrequencySets::expandFrequencies()
  190. {
  191. //dump("before expand freq", spectrum);
  192. expandFrequencies(harmonics, spectrum);
  193. expandFrequencies(alias, spectrum);
  194. //dump("after expand freq", spectrum);
  195. assert(checkOverlap());
  196. }
  197. inline bool FrequencySets::checkOverlap() const
  198. {
  199. std::vector<double> overlap;
  200. std::set_intersection(harmonics.begin(), harmonics.end(),
  201. alias.begin(), alias.end(),
  202. std::back_inserter(overlap));
  203. if (!overlap.empty()) {
  204. for (auto x : overlap) {
  205. printf("overlap at %f\n", x);
  206. }
  207. }
  208. return overlap.empty();
  209. }
  210. /*
  211. Ra = 12194**2 * f**4 /
  212. (f**2 + 20.6 ** 2) sqrt((f2 + 107.2**2)(f2 + 737.9**2)) * (f2 + 12194**2)
  213. A(f) = db(Ra) + 2
  214. */
  215. double WeightA(double mag, double f)
  216. {
  217. double num = (12194 * 12194) * f*f*f*f;
  218. double den = (f*f + 20.6*20.6) * sqrt((f*f + 107.2*107.2) * (f*f + 737.9 * 737.9)) * (f*f + 12194 * 12194);
  219. double Ra = num / den;
  220. // printf("Ra(%f) = %f\n", f, Ra);
  221. return Ra * mag;
  222. }
  223. void testAlias(std::function<float()> func, double fundamental, int numSamples)
  224. {
  225. // printf("test alias fundamental=%f,%f,%f\n", fundamental, fundamental * 2, fundamental * 3);
  226. FFTDataCpx spectrum(numSamples);
  227. Analyzer::getSpectrum(spectrum, false, func);
  228. FrequencySets frequencies(fundamental, sampleRate, spectrum);
  229. assert(frequencies.checkOverlap());
  230. // frequencies.dump("init freq");
  231. if (adjustBins)
  232. frequencies.adjustFrequencies();
  233. // frequencies.dump("after adjust");
  234. assert(frequencies.checkOverlap());
  235. if (expandBins)
  236. frequencies.expandFrequencies();
  237. assert(frequencies.checkOverlap());
  238. //frequencies.dump("final freq");
  239. double totalSignal = 0;
  240. double totalAlias = 0;
  241. double totalSignalA = 0;
  242. double totalAliasA = 0;
  243. double totalAliasOver5 = 0;
  244. double totalAliasBelow5 = 0;
  245. // let's look at every spectrum line
  246. for (int i = 1; i < numSamples / 2; ++i) {
  247. const double freq = FFT::bin2Freq(i, sampleRate, numSamples);
  248. const double mag = spectrum.getAbs(i);
  249. // const double db = AudioMath::db(mag);
  250. const double magA = WeightA(mag, freq);
  251. const bool isH2 = frequencies.harmonics.find(freq) != frequencies.harmonics.end();
  252. const bool isA2 = frequencies.alias.find(freq) != frequencies.alias.end();
  253. assert(!isA2 || !isH2);
  254. const bool above5k = (freq >= 5000);
  255. // const bool above10k = (freq > 10000);
  256. if (isH2) {
  257. totalSignal += mag;
  258. totalSignalA += magA;
  259. }
  260. if (isA2) {
  261. totalAlias += mag;
  262. totalAliasA += magA;
  263. if (above5k) {
  264. totalAliasOver5 += mag;
  265. } else {
  266. totalAliasBelow5 += mag;
  267. }
  268. }
  269. }
  270. printf("total sig = %f alias = %f ratiodb=%f A=%f\n",
  271. totalSignal,
  272. totalAlias,
  273. AudioMath::db(totalAlias / totalSignal),
  274. 2 + AudioMath::db(totalAliasA / totalSignalA)
  275. );
  276. }
  277. void printHeader(const char * label, double desired, double actual)
  278. {
  279. printf("\n%s freq = %f, round %f\n", label, desired, actual);
  280. printf("frame size = %d, expandThreshDb=%f \n", numSamples, expandThresholdDb);
  281. printf("expand bins=%d, adjustBins=%d\n", expandBins, adjustBins);
  282. }
  283. template <typename T>
  284. void testRawSaw(double normalizedFreq)
  285. {
  286. const int numSamples = 64 * 1024;
  287. // adjust the freq to even
  288. double freq = Analyzer::makeEvenPeriod(sampleRate * normalizedFreq, sampleRate, numSamples);
  289. printHeader("Raw Saw", sampleRate * normalizedFreq, freq);
  290. // make saw osc at correct freq
  291. SawOscillatorParams<T> params;
  292. SawOscillatorState<T> state;
  293. SawOscillator<T, false>::setFrequency(params, (float) normalizedFreq);
  294. testAlias([&state, &params]() {
  295. return (T) 30 * SawOscillator<T, false>::runSaw(state, params);
  296. }, freq, numSamples);
  297. }
  298. static void testEven(double normalizedFreq)
  299. {
  300. // adjust the freq to even
  301. double freq = Analyzer::makeEvenPeriod(sampleRate * normalizedFreq, sampleRate, numSamples);
  302. printHeader("EvenVCO", sampleRate * normalizedFreq, freq);
  303. using EVCO = EvenVCO <TestComposite>;
  304. EVCO vco;
  305. vco._testFreq = float(sampleRate * normalizedFreq);
  306. vco.outputs[EVCO::SAW_OUTPUT].active = true;
  307. vco.outputs[EVCO::SINE_OUTPUT].active = false;
  308. vco.outputs[EVCO::TRI_OUTPUT].active = false;
  309. vco.outputs[EVCO::SQUARE_OUTPUT].active = false;
  310. vco.outputs[EVCO::EVEN_OUTPUT].active = false;
  311. testAlias([&vco]() {
  312. vco.step();
  313. return 3 * vco.outputs[EVCO::SAW_OUTPUT].value;
  314. }, freq, numSamples);
  315. fflush(stdout);
  316. }
  317. #if 0 // most builds don't have orig
  318. static void testAliasFunOrig(double normalizedFreq)
  319. {
  320. // adjust the freq to even
  321. double freq = Analyzer::makeEvenPeriod(sampleRate * normalizedFreq, sampleRate, numSamples);
  322. printHeader("FunOrig", sampleRate * normalizedFreq, freq);
  323. VoltageControlledOscillatorOrig<16, 16> vco;
  324. vco.freq = float(sampleRate * normalizedFreq);
  325. vco.sampleTime = 1.0f / sampleRate;
  326. testAlias([&vco]() {
  327. const float deltaTime = 1.0f / sampleRate;
  328. vco.process(deltaTime, 0);
  329. return 15 * vco.saw();
  330. }, freq, numSamples);
  331. }
  332. #endif
  333. static void testAliasFun(double normalizedFreq)
  334. {
  335. // adjust the freq to even
  336. double freq = Analyzer::makeEvenPeriod(sampleRate * normalizedFreq, sampleRate, numSamples);
  337. printHeader("Fun Mine", sampleRate * normalizedFreq, freq);
  338. VoltageControlledOscillator<16, 16> vco;
  339. vco.freq = float(sampleRate * normalizedFreq);
  340. vco.sampleTime = 1.0f / sampleRate;
  341. vco.sawEnabled = true;
  342. vco.sinEnabled = false;
  343. vco.sqEnabled = false;
  344. vco.triEnabled = false;
  345. vco.init();
  346. testAlias([&vco]() {
  347. const float deltaTime = 1.0f / sampleRate;
  348. vco.process(deltaTime, 0);
  349. return 15 * vco.saw();
  350. }, freq, numSamples);
  351. }
  352. /*
  353. First try:
  354. desired freq = 844.180682, round 842.486572
  355. test alias fundamental=842.486572,1684.973145,2527.459717
  356. total sig = 3.239564 alias = 0.100040 ratiodb=-30.206276
  357. desired freq = 1688.361365, round 1687.664795
  358. test alias fundamental=1687.664795,3375.329590,5062.994385
  359. total sig = 6.824180 alias = 0.158808 ratiodb=-32.663559
  360. desired freq = 3376.722729, round 3375.329590
  361. test alias fundamental=3375.329590,6750.659180,10125.988770
  362. total sig = 3.512697 alias = 0.166856 ratiodb=-26.465975
  363. Test passed. Press any key to continue...
  364. ---
  365. EvenVCO freq = 844.180682, round 843.832397
  366. frame size = 65536, expandThreshDb=0.000000
  367. expand bins=0, adjustBins=1
  368. adjust moved 6939
  369. total sig = 0.015563 alias = 0.015805 ratiodb=0.133833
  370. Raw Saw freq = 844.180682, round 843.832397
  371. frame size = 65536, expandThreshDb=0.000000
  372. expand bins=0, adjustBins=1
  373. adjust moved 392
  374. total sig = 0.166494 alias = 0.041957 ratiodb=-11.971910
  375. Raw Saw freq = 1688.361365, round 1688.337708
  376. frame size = 65536, expandThreshDb=0.000000
  377. expand bins=0, adjustBins=1
  378. adjust moved 0
  379. total sig = 14.344683 alias = 1.279057 ratiodb=-20.996020
  380. Raw Saw freq = 3376.722729, round 3376.675415
  381. frame size = 65536, expandThreshDb=0.000000
  382. expand bins=0, adjustBins=1
  383. adjust moved 0
  384. total sig = 10.917767 alias = 1.400212 ratiodb=-17.838803
  385. Test passed. Press any key to continue...
  386. ----
  387. EvenVCO freq = 844.180682, round 843.832397
  388. frame size = 65536, expandThreshDb=0.000000
  389. expand bins=1, adjustBins=1
  390. adjust moved 6939
  391. total sig = 0.015563 alias = 30.322415 ratiodb=65.793436
  392. Raw Saw freq = 844.180682, round 843.832397
  393. frame size = 65536, expandThreshDb=0.000000
  394. expand bins=1, adjustBins=1
  395. adjust moved 392
  396. total sig = 0.166494 alias = 0.368204 ratiodb=6.893811
  397. Raw Saw freq = 1688.361365, round 1688.337708
  398. frame size = 65536, expandThreshDb=0.000000
  399. expand bins=1, adjustBins=1
  400. adjust moved 0
  401. total sig = 14.344683 alias = 3.708226 ratiodb=-11.750495
  402. Raw Saw freq = 3376.722729, round 3376.675415
  403. frame size = 65536, expandThreshDb=0.000000
  404. expand bins=1, adjustBins=1
  405. adjust moved 0
  406. total sig = 10.917767 alias = 3.809961 ratiodb=-9.144265
  407. Test passed. Press any key to continue...
  408. */
  409. void testVCOAlias()
  410. {
  411. testPitchQuantize();
  412. for (int i = 2; i <= 8; i *= 2) {
  413. float f = 1.0f / (i * 6.53f);
  414. // testEven(f);
  415. // testRawSaw<float>(f);
  416. // testAliasFunOrig(f);
  417. testAliasFun(f);
  418. }
  419. }