| 
							- 
 - #include "Analyzer.h"
 - #include "asserts.h"
 - #include "EvenVCO.h"
 - #include "FunVCO.h"
 - #include "SawOscillator.h"
 - #include "SinOscillator.h"
 - #include "TestComposite.h"
 - 
 - 
 - // globals for these tests
 - static const float sampleRate = 44100;
 - static bool expandBins = false;
 - static bool adjustBins = true;
 - #if 0
 - static const int numSamples = 64 * 1024;
 - static const double expandThresholdDb = -10;
 - #else
 - static const int numSamples = 64 * 1024;
 - static const double expandThresholdDb = .01;          // normally negative
 - #endif
 - 
 - 
 - 
 - 
 - static void testPitchQuantize()
 - {
 -     const double sampleRate = 44100;
 -     const int numSamples = 16;
 -     const double inputFreq = 44100.0 / 4.0;
 -     double freq = Analyzer::makeEvenPeriod(inputFreq, sampleRate, numSamples);
 - 
 -     // check that quantized pitch is in the bin we expect.
 -     assertEQ(freq, FFT::bin2Freq(4, sampleRate, numSamples));
 - 
 -     // make saw osc at correct freq
 -     SinOscillatorParams<double> params;
 -     SinOscillatorState<double> state;
 -     SinOscillator<double, false>::setFrequency(params, 1.0 / 4.0);
 - 
 -     // check that spectrum has only a single freq
 -     std::function<float()> func = [&state, ¶ms]() {
 -         return float(30 * SinOscillator<double, false>::run(state, params));
 -     };
 -     FFTDataCpx spectrum(numSamples);
 - 
 - 
 -     Analyzer::getSpectrum(spectrum, false, func);
 -     for (int i = 0; i < numSamples / 2; ++i) {
 -         const float abs = spectrum.getAbs(i);
 -         if (i == 4) {
 -             assertGE(abs, .5);
 -         } else {
 -             assertLT(abs, 0.000000001);
 -         }
 -     }
 - }
 - 
 - class AliasStats
 - {
 - public:
 -     float totalAliasDb;
 -     float totalAliasAWeighted;
 -     float maxAliasFreq;
 - };
 - 
 - /*
 - 
 - Next: examine the spectrum. make sure all freq in spectrum are signal or alias
 - */
 - 
 - class FrequencySets
 - {
 - public:
 -     FrequencySets(double fundamental, double sampleRate, const FFTDataCpx& spectrum);
 -     void expandFrequencies();
 -     bool checkOverlap() const;
 - 
 -     std::set<double> harmonics;
 -     std::set<double> alias;
 - 
 -     void adjustFrequencies();
 -     void dump(const char *) const;
 - private:
 -     static void expandFrequencies(std::set<double>&, const FFTDataCpx& spectrum);
 -     bool adjustFrequencies1();
 -     static bool adjustFreqHelper(int bin, int tryBin, std::set<double>& set, const FFTDataCpx& spectrum);
 -     const FFTDataCpx& spectrum;
 - };
 - 
 - inline void FrequencySets::adjustFrequencies()
 - {
 -     int tries = 0;
 -     while (adjustFrequencies1()) {
 -         ++tries;
 -     }
 -     //printf("adjust moved %d\n", tries);
 - }
 - 
 - inline bool FrequencySets::adjustFreqHelper(int bin, int tryBin, std::set<double>& set, const FFTDataCpx& spectrum)
 - {
 -     bool ret = false;
 -     if (tryBin < 0 || tryBin >= spectrum.size()) {
 -         return false;
 -     }
 -     const double db = AudioMath::db(spectrum.getAbs(bin));
 -     const double dbm1 = AudioMath::db(spectrum.getAbs(tryBin));
 -     if (dbm1 > (db + 3)) {               // only adjust a bin if it's a 3db improvement
 -         const double f = FFT::bin2Freq(bin, sampleRate, spectrum.size());
 -         const double fNew = FFT::bin2Freq(tryBin, sampleRate, spectrum.size());
 -         auto x = set.erase(f);
 -         assert(x == 1);
 -         set.insert(fNew);
 -         ret = true;
 -     }
 - 
 -     return ret;
 - 
 - }
 - 
 - inline bool FrequencySets::adjustFrequencies1()
 - {
 -     for (auto f : harmonics) {
 -         const int bin = FFT::freqToBin(f, sampleRate, spectrum.size());
 -         if (adjustFreqHelper(bin, bin - 1, harmonics, spectrum))
 -             return true;
 -         if (adjustFreqHelper(bin, bin + 1, harmonics, spectrum))
 -             return true;
 -     }
 -     for (auto f : alias) {
 -         const int bin = FFT::freqToBin(f, sampleRate, spectrum.size());
 -         if (adjustFreqHelper(bin, bin - 1, alias, spectrum))
 -             return true;
 -         if (adjustFreqHelper(bin, bin + 1, alias, spectrum))
 -             return true;
 -     }
 -     return false;
 - }
 - 
 - 
 - inline FrequencySets::FrequencySets(double fundamental, double sampleRate, const FFTDataCpx& spectrum) :
 -     spectrum(spectrum)
 - {
 -     const double nyquist = sampleRate / 2;
 -     bool done = false;
 -     for (int i = 1; !done; ++i) {
 -         double freq = fundamental * i;
 -         if (freq < nyquist) {
 -             //harmonics.push_back(freq);
 -             harmonics.insert(freq);
 -         } else {
 -             double over = freq - nyquist;
 -             if (over < nyquist) {
 -                 freq = nyquist - over;
 -                 //alias.push_back(freq);
 -                 alias.insert(freq);
 -             } else {
 -                 done = true;
 -             }
 -         }
 -     }
 - }
 - 
 - 
 - inline void expandHelper(double& maxDb, bool& done, int& i, int deltaI, const FFTDataCpx& spectrum, std::set<double>& f)
 - {
 -     if (i >= spectrum.size() || i < 0) {
 -         done = true;
 -     } else {
 -         const double db = AudioMath::db(spectrum.getAbs(i));
 - 
 -         if (db < (maxDb + expandThresholdDb)) {
 -             done = true;
 -         } else {
 -             //const double oldFreq = FFT::bin2Freq(i, sampleRate, spectrum.size());
 -             const double newFreq = FFT::bin2Freq(i, sampleRate, spectrum.size());
 -             if (newFreq < 900 && newFreq > 800)
 -                 printf("inserting new freq %f db=%f m=%f\n ", newFreq, db, maxDb);
 -             maxDb = std::max(maxDb, db);
 -             f.insert(newFreq);
 -         }
 -     }
 -     i += deltaI;
 - }
 - 
 - inline void FrequencySets::expandFrequencies(std::set<double>& f, const FFTDataCpx& spectrum)
 - {
 -     assert(expandBins);
 -     for (double freq : f) {
 -         if (int(freq) == 1064) {
 -             int x = 5;
 -         }
 -         const int bin = FFT::freqToBin(freq, sampleRate, spectrum.size());
 -         double maxDb = AudioMath::db(spectrum.getAbs(bin));
 - 
 -         // search upward
 -         bool done;
 -         int i;
 -         for (i = bin + 1, done = false; !done; ) {
 -             expandHelper(maxDb, done, i, 1, spectrum, f);
 -         }
 - 
 -         for (i = bin - 1, done = false; !done; ) {
 -             expandHelper(maxDb, done, i, -1, spectrum, f);
 -         }
 -     }
 - }
 - 
 - inline void FrequencySets::dump(const char *label) const
 - {
 -     printf("**** %s ****\n", label);
 -     for (auto f : harmonics) {
 -         int bin = FFT::freqToBin(f, sampleRate, spectrum.size());
 -         printf("harm at %.2f db:%.2f\n", f, AudioMath::db(spectrum.getAbs(bin)));
 -     }
 -     for (auto f : alias) {
 -         int bin = FFT::freqToBin(f, sampleRate, spectrum.size());
 -         printf("alias at %.2f db:%.2f\n", f, AudioMath::db(spectrum.getAbs(bin)));
 -     }
 - }
 - 
 - inline void FrequencySets::expandFrequencies()
 - {
 -     //dump("before expand freq", spectrum);
 -     expandFrequencies(harmonics, spectrum);
 -     expandFrequencies(alias, spectrum);
 - 
 - 
 -     //dump("after expand freq", spectrum);
 -     assert(checkOverlap());
 - 
 - }
 - 
 - inline bool FrequencySets::checkOverlap() const
 - {
 -     std::vector<double> overlap;
 - 
 -     std::set_intersection(harmonics.begin(), harmonics.end(),
 -         alias.begin(), alias.end(),
 -         std::back_inserter(overlap));
 -     if (!overlap.empty()) {
 -         for (auto x : overlap) {
 -             printf("overlap at %f\n", x);
 -         }
 -     }
 -     return overlap.empty();
 - }
 - 
 - 
 - /*
 - 
 - 
 - Ra = 12194**2 * f**4 /
 - (f**2 + 20.6 ** 2) sqrt((f2 + 107.2**2)(f2 + 737.9**2)) * (f2 + 12194**2)
 - A(f) = db(Ra) + 2
 - */
 - double WeightA(double mag, double f)
 - {
 -     double num = (12194 * 12194) * f*f*f*f;
 -     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);
 -     double Ra = num / den;
 -    // printf("Ra(%f) = %f\n", f, Ra);
 -     return Ra * mag;
 - }
 - 
 - void testAlias(std::function<float()> func, double fundamental, int numSamples)
 - {
 -    // printf("test alias fundamental=%f,%f,%f\n", fundamental, fundamental * 2, fundamental * 3);
 -     FFTDataCpx spectrum(numSamples);
 -     Analyzer::getSpectrum(spectrum, false, func);
 -     FrequencySets frequencies(fundamental, sampleRate, spectrum);
 -     assert(frequencies.checkOverlap());
 - 
 -   // frequencies.dump("init freq");
 -     if (adjustBins)
 -         frequencies.adjustFrequencies();
 -   //  frequencies.dump("after adjust");
 -     assert(frequencies.checkOverlap());
 - 
 -     if (expandBins)
 -         frequencies.expandFrequencies();
 -     assert(frequencies.checkOverlap());
 - 
 -     //frequencies.dump("final freq");
 - 
 -     double totalSignal = 0;
 -     double totalAlias = 0;
 -     double totalSignalA = 0;
 -     double totalAliasA = 0;
 -     double totalAliasOver5 = 0;
 -     double totalAliasBelow5 = 0;
 - 
 -     // let's look at every spectrum line
 -     for (int i = 1; i < numSamples / 2; ++i) {
 -         const double freq = FFT::bin2Freq(i, sampleRate, numSamples);
 -         const double mag = spectrum.getAbs(i);
 -     //    const double db = AudioMath::db(mag);
 -         const double magA = WeightA(mag, freq);
 - 
 -         const bool isH2 = frequencies.harmonics.find(freq) != frequencies.harmonics.end();
 -         const bool isA2 = frequencies.alias.find(freq) != frequencies.alias.end();
 - 
 -         assert(!isA2 || !isH2);
 - 
 -         const bool above5k = (freq >= 5000);
 -      //   const bool above10k = (freq > 10000);
 - 
 -         if (isH2) {
 -             totalSignal += mag;
 -             totalSignalA += magA;
 -         }
 -         if (isA2) {
 -             totalAlias += mag;
 -             totalAliasA += magA;
 -             if (above5k) {
 -                 totalAliasOver5 += mag;
 -             } else {
 -                 totalAliasBelow5 += mag;
 -             }
 -         }
 -     }
 - 
 -     printf("total sig = %f alias = %f ratiodb=%f A=%f\n",
 -         totalSignal,
 -         totalAlias,
 -         AudioMath::db(totalAlias / totalSignal),
 -         2 + AudioMath::db(totalAliasA / totalSignalA)
 -     );
 - }
 - 
 - void printHeader(const char * label, double desired, double actual)
 - {
 -     printf("\n%s freq = %f, round %f\n", label, desired, actual);
 -     printf("frame size = %d, expandThreshDb=%f \n", numSamples, expandThresholdDb);
 -     printf("expand bins=%d, adjustBins=%d\n", expandBins, adjustBins);
 - }
 - 
 - template <typename T>
 - void testRawSaw(double normalizedFreq)
 - {
 -     const int numSamples = 64 * 1024;
 -     // adjust the freq to even
 - 
 -     double freq = Analyzer::makeEvenPeriod(sampleRate * normalizedFreq, sampleRate, numSamples);
 -     printHeader("Raw Saw", sampleRate * normalizedFreq, freq);
 - 
 -     // make saw osc at correct freq
 -     SawOscillatorParams<T> params;
 -     SawOscillatorState<T> state;
 -     SawOscillator<T, false>::setFrequency(params, (float) normalizedFreq);
 -     testAlias([&state, ¶ms]() {
 -         return (T) 30 * SawOscillator<T, false>::runSaw(state, params);
 -         }, freq, numSamples);
 - 
 - }
 - 
 - static void testEven(double normalizedFreq)
 - {
 - 
 -     // adjust the freq to even
 -     double freq = Analyzer::makeEvenPeriod(sampleRate * normalizedFreq, sampleRate, numSamples);
 -     printHeader("EvenVCO", sampleRate * normalizedFreq, freq);
 - 
 -     using EVCO = EvenVCO <TestComposite>;
 -     EVCO vco;
 -     vco._testFreq = float(sampleRate * normalizedFreq);
 -     vco.outputs[EVCO::SAW_OUTPUT].active = true;
 -     vco.outputs[EVCO::SINE_OUTPUT].active = false;
 -     vco.outputs[EVCO::TRI_OUTPUT].active = false;
 -     vco.outputs[EVCO::SQUARE_OUTPUT].active = false;
 -     vco.outputs[EVCO::EVEN_OUTPUT].active = false;
 - 
 -     testAlias([&vco]() {
 -         vco.step();
 -         return 3 * vco.outputs[EVCO::SAW_OUTPUT].value;
 -         }, freq, numSamples);
 - 
 -     fflush(stdout);
 - }
 - 
 - #if 0 // most builds don't have orig
 - static void testAliasFunOrig(double normalizedFreq)
 - {
 -     // adjust the freq to even
 -     double freq = Analyzer::makeEvenPeriod(sampleRate * normalizedFreq, sampleRate, numSamples);
 -     printHeader("FunOrig", sampleRate * normalizedFreq, freq);
 - 
 -     VoltageControlledOscillatorOrig<16, 16> vco;
 -     vco.freq = float(sampleRate * normalizedFreq);
 -     vco.sampleTime = 1.0f / sampleRate;
 - 
 -     testAlias([&vco]() {
 -         const float deltaTime = 1.0f / sampleRate;
 -         vco.process(deltaTime, 0);
 -         return 15 * vco.saw();
 -         }, freq, numSamples);
 - }
 - #endif
 - 
 - 
 - static void testAliasFun(double normalizedFreq)
 - {
 -     // adjust the freq to even
 -     double freq = Analyzer::makeEvenPeriod(sampleRate * normalizedFreq, sampleRate, numSamples);
 -     printHeader("Fun Mine", sampleRate * normalizedFreq, freq);
 - 
 -     VoltageControlledOscillator<16, 16> vco;
 -     vco.freq = float(sampleRate * normalizedFreq);
 -     vco.sampleTime = 1.0f / sampleRate;
 - 
 -     vco.sawEnabled = true;
 -     vco.sinEnabled = false;
 -     vco.sqEnabled = false;
 -     vco.triEnabled = false;
 -     vco.init();
 - 
 -     testAlias([&vco]() {
 -         const float deltaTime = 1.0f / sampleRate;
 -         vco.process(deltaTime, 0);
 -         return 15 * vco.saw();
 -         }, freq, numSamples);
 - }
 - 
 - 
 - /*
 - First try:
 - desired freq = 844.180682, round 842.486572
 - test alias fundamental=842.486572,1684.973145,2527.459717
 - total sig = 3.239564 alias = 0.100040 ratiodb=-30.206276
 - 
 - desired freq = 1688.361365, round 1687.664795
 - test alias fundamental=1687.664795,3375.329590,5062.994385
 - total sig = 6.824180 alias = 0.158808 ratiodb=-32.663559
 - 
 - desired freq = 3376.722729, round 3375.329590
 - test alias fundamental=3375.329590,6750.659180,10125.988770
 - total sig = 3.512697 alias = 0.166856 ratiodb=-26.465975
 - Test passed. Press any key to continue...
 - 
 - ---
 - 
 - EvenVCO freq = 844.180682, round 843.832397
 - frame size = 65536, expandThreshDb=0.000000
 - expand bins=0, adjustBins=1
 - adjust moved 6939
 - total sig = 0.015563 alias = 0.015805 ratiodb=0.133833
 - 
 - Raw Saw freq = 844.180682, round 843.832397
 - frame size = 65536, expandThreshDb=0.000000
 - expand bins=0, adjustBins=1
 - adjust moved 392
 - total sig = 0.166494 alias = 0.041957 ratiodb=-11.971910
 - 
 - Raw Saw freq = 1688.361365, round 1688.337708
 - frame size = 65536, expandThreshDb=0.000000
 - expand bins=0, adjustBins=1
 - adjust moved 0
 - total sig = 14.344683 alias = 1.279057 ratiodb=-20.996020
 - 
 - Raw Saw freq = 3376.722729, round 3376.675415
 - frame size = 65536, expandThreshDb=0.000000
 - expand bins=0, adjustBins=1
 - adjust moved 0
 - total sig = 10.917767 alias = 1.400212 ratiodb=-17.838803
 - Test passed. Press any key to continue...
 - 
 - ----
 - 
 - EvenVCO freq = 844.180682, round 843.832397
 - frame size = 65536, expandThreshDb=0.000000
 - expand bins=1, adjustBins=1
 - adjust moved 6939
 - total sig = 0.015563 alias = 30.322415 ratiodb=65.793436
 - 
 - Raw Saw freq = 844.180682, round 843.832397
 - frame size = 65536, expandThreshDb=0.000000
 - expand bins=1, adjustBins=1
 - adjust moved 392
 - total sig = 0.166494 alias = 0.368204 ratiodb=6.893811
 - 
 - Raw Saw freq = 1688.361365, round 1688.337708
 - frame size = 65536, expandThreshDb=0.000000
 - expand bins=1, adjustBins=1
 - adjust moved 0
 - total sig = 14.344683 alias = 3.708226 ratiodb=-11.750495
 - 
 - Raw Saw freq = 3376.722729, round 3376.675415
 - frame size = 65536, expandThreshDb=0.000000
 - expand bins=1, adjustBins=1
 - adjust moved 0
 - total sig = 10.917767 alias = 3.809961 ratiodb=-9.144265
 - Test passed. Press any key to continue...
 - 
 - 
 - 
 - 
 - */
 - 
 - void testVCOAlias()
 - {
 -     testPitchQuantize();
 - 
 - 
 -     for (int i = 2; i <= 8; i *= 2) {
 -         float f = 1.0f / (i * 6.53f);
 -       //  testEven(f);
 -        // testRawSaw<float>(f);
 -        // testAliasFunOrig(f);
 -         testAliasFun(f);
 -     }
 - }
 
 
  |