#include "asserts.h" #include "ExtremeTester.h" #include "VocalAnimator.h" #include "TestComposite.h" #include "VocalFilter.h" #include "FormantTables2.h" using Animator = VocalAnimator; /** * Verify no output with no input. */ static void test0() { Animator anim; anim.setSampleRate(44100); anim.init(); anim.outputs[Animator::AUDIO_OUTPUT].value = 0; anim.step(); // prime it // with no input, should have no output for (int i = 0; i < 50; ++i) { anim.step(); assert(anim.outputs[Animator::AUDIO_OUTPUT].value == 0); } } /** * Verify output with input. */ static void test1() { Animator anim; anim.setSampleRate(44100); anim.init(); anim.outputs[Animator::AUDIO_OUTPUT].value = 0; anim.inputs[Animator::AUDIO_INPUT].value = 1; anim.step(); // prime it // with input, should have output for (int i = 0; i < 50; ++i) { anim.step(); assert(anim.outputs[Animator::AUDIO_OUTPUT].value != 0); } } /** * Verify filter settings with no mod. */ static void test2() { Animator anim; anim.setSampleRate(44100); anim.init(); for (int i = 0; i < 4; ++i) { float freq = anim.normalizedFilterFreq[i] * 44100; assertEQ(freq, anim.nominalFilterCenterHz[i]); } } /** * Verify filter settings respond to Fc. */ static void test3() { Animator anim; anim.setSampleRate(44100); anim.init(); anim.params[anim.FILTER_FC_PARAM].value = 0; anim.step(); for (int i = 0; i < 4; ++i) { // assert(anim.filterFrequency[i] == anim.nominalFilterCenter[i]); float freq = anim.normalizedFilterFreq[i] * 44100; assertClose(freq, anim.nominalFilterCenterHz[i], 1); } anim.params[anim.FILTER_FC_PARAM].value = 1; anim.step(); // assert that when we shift up, the expected values shift up for (int i = 0; i < 4; ++i) { float freq = anim.normalizedFilterFreq[i] * 44100; //printf("i=%d, freq=%f, nominal=%f\n", i, freq, anim.nominalFilterCenterHz[i]); if (i == 3) { assertClose(freq, anim.nominalFilterCenterHz[i], 1); } else assert(freq > anim.nominalFilterCenterHz[i]); } #if 0 anim.params[anim.FILTER_FC_PARAM].value = -1; anim.step(); for (int i = 0; i < 4; ++i) { if (i == 3) assert(anim.filterFrequency[i] == anim.nominalFilterCenter[i]); else assert(anim.filterFrequency[i] < anim.nominalFilterCenter[i]); } #endif } static void testScalers() { Animator anim; anim.setSampleRate(44100); anim.init(); // cv/knob, trim // cases with no CV assertClose(.5, anim.scale0_1(0, 0, 1), .001); // knob half, full trim assertClose(.5, anim.scale0_1(0, 0, -1), .001); // knob half, full neg trim assertClose(1, anim.scale0_1(0, 5, 0), .001); // knob full assertClose(0, anim.scale0_1(0, -5, 0), .001); // knob down full assertClose(.75, anim.scale0_1(0, (5.0f * .5f), 0), .001); // knob 3/4 // CV, no knob assertClose(1, anim.scale0_1(5, 0, 1), .001); // full cv, untrimmed assertClose(0, anim.scale0_1(-5, 0, 1), .001); // full cv, untrimmed assertClose(.25, anim.scale0_1((-5.0f * .5f), 0, 1), .001); // 3/4 cv, untrimmed // assertClose(.75, anim.scale0_1(5, 0, .5f), .001); // full cv, half trim assertClose(0, anim.scale0_1(5, 0, -1), .001); // full cv, full neg trim } #if 0 static void dump(const char * msg, const Animator& anim) { std::cout << "dumping " << msg << "\nfiltFreq" << " " << std::pow(2, anim.filterFrequencyLog[0]) << " " << std::pow(2, anim.filterFrequencyLog[1]) << " " << std::pow(2, anim.filterFrequencyLog[2]) << " " << std::pow(2, anim.filterFrequencyLog[3]) << std::endl; } static void x() { Animator anim; anim.setSampleRate(44100); anim.init(); anim.params[anim.FILTER_FC_PARAM].value = 0; anim.step(); dump("init", anim); // TODO: assert here anim.params[anim.FILTER_FC_PARAM].value = 5; anim.step(); dump("fc 5", anim); anim.params[anim.FILTER_FC_PARAM].value = -5; anim.step(); dump("fc -5", anim); std::cout << "\nabout to modulate up. maxLFO, def depth\n"; anim.params[anim.FILTER_FC_PARAM].value = 0; anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 0; anim.jamModForTest = true; anim.modValueForTest = 5; anim.step(); dump("max up def", anim); std::cout << "\nabout to modulate up. minLFO, def depth\n"; anim.params[anim.FILTER_FC_PARAM].value = 0; anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 0; anim.jamModForTest = true; anim.modValueForTest = -5; anim.step(); dump("max down def", anim); std::cout << "\nabout to modulate up. maxLFO, max depth\n"; anim.params[anim.FILTER_FC_PARAM].value = 0; anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 5; anim.jamModForTest = true; anim.modValueForTest = 5; anim.step(); dump(" modulate up. maxLFO, max depthf", anim); std::cout << "\nabout to modulate down. minLFO, max depth\n"; anim.params[anim.FILTER_FC_PARAM].value = 0; anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 5; anim.jamModForTest = true; anim.modValueForTest = -5; anim.step(); dump(" modulate up. maxLFO, max depthf", anim); #if 0 // TODO: would be nice to be able to inject an LFO voltage anim.params[anim.FILTER_FC_PARAM].value = 0; anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = 5; for (int i = 0; i < 40000; ++i) { anim.step(); } dump("fc 0 depth 1", anim); std::cout << "about to to depth -\n"; // TODO: would be nice to be able to inject an LFO voltage anim.params[anim.FILTER_FC_PARAM].value = 0; anim.params[anim.FILTER_MOD_DEPTH_PARAM].value = -5; for (int i = 0; i < 4000; ++i) { anim.step(); } dump("fc 0 depth -5", anim); #endif } #endif /** *Interpolates the frequency using lookups * @param model = 0(bass) 1(tenor) 2(countertenor) 3(alto) 4(soprano) * @param index = 0..4 (formant F1..F5) * @param vowel is the continuous index into the per / vowel lookup tables(0..4) * 0 = a, 1 = e, 2 = i, 3 = o 4 = u */ //float getLogFrequency(int model, int index, float vowel) static void testFormantTables() { FormantTables2 ff; float x = ff.getLogFrequency(0, 0, 0); assert(x > 0); x = ff.getNormalizedBandwidth(0, 0, 0); assert(x > 0); x = ff.getGain(0, 0, 0); #if 1 // store DB, not gain assert(x <= 0); assert(x >= -62); #else assert(x > 0) #endif // spot check a few freq // formant F2 of alto, 'u' x = ff.getLogFrequency(3, 1, 4); assertClose(x, std::log2(700), .0001); // formant F3 of soprano, 'o' x = ff.getLogFrequency(4, 2, 3); assertClose(x, std::log2(2830), .0001); } static void testFormantTables2() { FormantTables2 ff; for (int model = 0; model < FormantTables2::numModels; ++model) { for (int formantBand = 0; formantBand < FormantTables2::numFormantBands; ++formantBand) { for (int vowel = 0; vowel < FormantTables2::numVowels; ++vowel) { const float f = ff.getLogFrequency(model, formantBand, float(vowel)); // check that the frequencies are possible formants assert(std::pow(2, f) > 100); assert(std::pow(2, f) < 5500); const float nBw = ff.getNormalizedBandwidth(model, formantBand, float(vowel)); assert(nBw < .5); assert(nBw > .01); // db now const float gain = ff.getGain(model, formantBand, float(vowel)); assertLE(gain, 0); assertGT(gain, -70); // assertLE(gain, 1); // assert(gain > 0); } } } } static void testVocalFilter() { VocalFilter vf; vf.setSampleRate(44100); vf.init(); vf.outputs[VocalFilter::AUDIO_OUTPUT].value = 0; vf.inputs[VocalFilter::AUDIO_INPUT].value = 1; vf.step(); // prime it // with input, should have output for (int i = 0; i < 50; ++i) { vf.step(); assert(vf.outputs[VocalFilter::AUDIO_OUTPUT].value != 0); } } static void testInputExtremes() { VocalAnimator va; va.setSampleRate(44100); va.init(); using fp = std::pair; std::vector< std::pair > paramLimits; paramLimits.resize(va.NUM_PARAMS); paramLimits[va.LFO_RATE_PARAM] = fp(-5.0f, 5.0f); // paramLimits[va.LFO_SPREAD_PARAM] = fp(-5.0f, 5.0f); paramLimits[va.FILTER_FC_PARAM] = fp(-5.0f, 5.0f); paramLimits[va.FILTER_Q_PARAM] = fp(-5.0f, 5.0f); paramLimits[va.FILTER_MOD_DEPTH_PARAM] = fp(-5.0f, 5.0f); paramLimits[va.LFO_RATE_TRIM_PARAM] = fp(-1.0f, 1.0f); paramLimits[va.FILTER_Q_TRIM_PARAM] = fp(-1.0f, 1.0f); paramLimits[va.FILTER_FC_TRIM_PARAM] = fp(-1.0f, 1.0f); paramLimits[va.FILTER_MOD_DEPTH_TRIM_PARAM] = fp(-1.0f, 1.0f); paramLimits[va.BASS_EXP_PARAM] = fp(0.f, 1.0f); paramLimits[va.TRACK_EXP_PARAM] = fp(0.f, 2.0f); paramLimits[va.LFO_MIX_PARAM] = fp(0.f, 1.0f); // TODO: why is output going so high? ExtremeTester< VocalAnimator>::test(va, paramLimits, false, "vocal animator"); } static void testVocalExtremes() { VocalFilter va; va.setSampleRate(44100); va.init(); using fp = std::pair; std::vector< std::pair > paramLimits; paramLimits.resize(va.NUM_PARAMS); paramLimits[va.FILTER_Q_PARAM] = fp(-5.0f, 5.0f); paramLimits[va.FILTER_Q_TRIM_PARAM] = fp(-1.0f, 1.0f); paramLimits[va.FILTER_FC_PARAM] = fp(-5.0f, 5.0f); paramLimits[va.FILTER_FC_TRIM_PARAM] = fp(-1.0f, 1.0f); paramLimits[va.FILTER_VOWEL_PARAM] = fp(-5.f, 5.0f); paramLimits[va.FILTER_VOWEL_TRIM_PARAM] = fp(-1.f, 1.0f); paramLimits[va.FILTER_MODEL_SELECT_PARAM] = fp(0.f, 4.0f); paramLimits[va.FILTER_BRIGHTNESS_PARAM] = fp(-5.f, 5.0f); paramLimits[va.FILTER_BRIGHTNESS_TRIM_PARAM] = fp(-1.0f, 1.0f); ExtremeTester< VocalFilter>::test(va, paramLimits, false, "vocal filter"); } void testVocalAnimator() { test0(); test1(); test2(); test3(); testScalers(); testFormantTables(); testFormantTables2(); testVocalFilter(); #if defined(_DEBUG) && true printf("skipping extremes\n"); #else testVocalExtremes(); testInputExtremes(); #endif }