/** * This file contains a modified version of EvenVCO.cpp, from the * Befaco repo. See LICENSE-dist.txt for full license info. * * This code has been modified extensively by Squinky Labs. Mainly modifications were: * re-code hot-spots to lower CPU usage. * Fix compiler warnings. * Make it compile in Visual Studio */ // Need to make this compile in MS tools for unit tests #if defined(_MSC_VER) #define __attribute__(x) #pragma warning (push) #pragma warning ( disable: 4244 4267 ) #endif #include "SqMath.h" #include "SqBlep.h" #include "AudioMath.h" #include "ObjectCache.h" using namespace rack; template struct EvenVCO : TBase { EvenVCO(struct Module * module); EvenVCO(); enum ParamIds { OCTAVE_PARAM, TUNE_PARAM, PWM_PARAM, NUM_PARAMS }; enum InputIds { PITCH1_INPUT, PITCH2_INPUT, FM_INPUT, PWM_INPUT, NUM_INPUTS }; enum OutputIds { TRI_OUTPUT, SINE_OUTPUT, EVEN_OUTPUT, SAW_OUTPUT, SQUARE_OUTPUT, NUM_OUTPUTS }; enum LightIds { NUM_LIGHTS }; float phase = 0.0; float tri = 0.0; /** * References to shared lookup tables. * Destructor will free them automatically. */ std::shared_ptr> sinLookup; std::function expLookup; /** Whether we are past the pulse width already */ bool halfPhase = false; SqBlep triSquareMinBLEP; SqBlep triMinBLEP; SqBlep sineMinBLEP; SqBlep doubleSawMinBLEP; SqBlep sawMinBLEP; SqBlep squareMinBLEP; void step() override; void step_even(float deltaPhase); void step_saw(float deltaPhase); void step_sq(float deltaPhase); void step_sin(float deltaPhase); void step_tri(float deltaPhase); void step_all(float deltaPhase); void step_old(); void initialize(); void zeroOutputsExcept(int except); int dispatcher = 0; int loopCounter = 0; /** * To avoid scanning outputs for changes every sample, we * save the state here. */ bool doSaw = false; bool doEven = false; bool doTri = false; bool doSq = false; bool doSin = false; /** * Variables added purely to enable unit testing */ float _freq = 0; float _testFreq = 0; }; template inline EvenVCO::EvenVCO() : TBase() { initialize(); } template inline EvenVCO::EvenVCO(struct Module * module) : TBase(module) { initialize(); } #ifdef __V1 using namespace rack::dsp; #else using namespace rack; #endif template inline void EvenVCO::initialize() { #if 0 triSquareMinBLEP.minblep = minblep_16_32; triSquareMinBLEP.oversample = 32; triMinBLEP.minblep = minblep_16_32; triMinBLEP.oversample = 32; sineMinBLEP.minblep = minblep_16_32; sineMinBLEP.oversample = 32; doubleSawMinBLEP.minblep = minblep_16_32; doubleSawMinBLEP.oversample = 32; sawMinBLEP.minblep = minblep_16_32; sawMinBLEP.oversample = 32; squareMinBLEP.minblep = minblep_16_32; squareMinBLEP.oversample = 32; #endif sinLookup = ObjectCache::getSinLookup(); expLookup = ObjectCache::getExp2Ex(); } template void EvenVCO::zeroOutputsExcept(int except) { for (int i = 0; i < NUM_OUTPUTS; ++i) { if (i != except) { // if we do even, we do sin at same time if ((i == SINE_OUTPUT) && (except == EVEN_OUTPUT)) { } else { TBase::outputs[i].value = 0; } } } } template inline void EvenVCO::step_even(float deltaPhase) { float oldPhase = phase; phase += deltaPhase; if (oldPhase < 0.5 && phase >= 0.5) { float crossing = -(phase - 0.5) / deltaPhase; doubleSawMinBLEP.jump(crossing, -2.0); } // Reset phase if at end of cycle if (phase >= 1.0) { phase -= 1.0; float crossing = -phase / deltaPhase; doubleSawMinBLEP.jump(crossing, -2.0); } //sine = -cosf(2*AudioMath::Pi * phase); // want cosine, but only have sine lookup float adjPhase = phase + .25f; if (adjPhase >= 1) { adjPhase -= 1; } const float sine = -LookupTable::lookup(*sinLookup, adjPhase, true); float doubleSaw = (phase < 0.5) ? (-1.0 + 4.0*phase) : (-1.0 + 4.0*(phase - 0.5)); doubleSaw += doubleSawMinBLEP.shift(); const float even = 0.55 * (doubleSaw + 1.27 * sine); TBase::outputs[SINE_OUTPUT].value = 5.0*sine; TBase::outputs[EVEN_OUTPUT].value = 5.0*even; } template inline void EvenVCO::step_saw(float deltaPhase) { phase += deltaPhase; // Reset phase if at end of cycle if (phase >= 1.0) { phase -= 1.0; float crossing = -phase / deltaPhase; static float cMin = 100; static float cMax = -100; cMin = std::min(crossing, cMin); cMax = std::max(crossing, cMax); // printf("sawJump ph=%.2f, delta=%.2f cross=%.2f (%.2f, %.2f)\n", phase, deltaPhase, crossing, cMin, cMax); sawMinBLEP.jump(crossing, -2.0); } float saw = -1.0 + 2.0*phase; saw += sawMinBLEP.shift(); TBase::outputs[SAW_OUTPUT].value = 5.0*saw; } template inline void EvenVCO::step_sin(float deltaPhase) { phase += deltaPhase; // Reset phase if at end of cycle if (phase >= 1.0) { phase -= 1.0; } // want cosine, but only have sine lookup float adjPhase = phase + .25f; if (adjPhase >= 1) { adjPhase -= 1; } const float sine = -LookupTable::lookup(*sinLookup, adjPhase, true); TBase::outputs[SINE_OUTPUT].value = 5.0*sine; } template inline void EvenVCO::step_tri(float deltaPhase) { float oldPhase = phase; phase += deltaPhase; if (oldPhase < 0.5 && phase >= 0.5) { const float crossing = -(phase - 0.5) / deltaPhase; triSquareMinBLEP.jump(crossing, 2.0); } // Reset phase if at end of cycle if (phase >= 1.0) { phase -= 1.0; float crossing = -phase / deltaPhase; triSquareMinBLEP.jump(crossing, -2.0); halfPhase = false; } // Outputs float triSquare = (phase < 0.5) ? -1.0 : 1.0; triSquare += triSquareMinBLEP.shift(); // Integrate square for triangle tri += 4.0 * triSquare * _freq * TBase::engineGetSampleTime(); tri *= (1.0 - 40.0 * TBase::engineGetSampleTime()); // Set output TBase::outputs[TRI_OUTPUT].value = 5.0*tri; } template inline void EvenVCO::step_sq(float deltaPhase) { phase += deltaPhase; // Pulse width float pw; if (doSq) { pw = TBase::params[PWM_PARAM].value + TBase::inputs[PWM_INPUT].value / 5.0; const float minPw = 0.05f; pw = sq::rescale(sq::clamp(pw, -1.0f, 1.0f), -1.0f, 1.0f, minPw, 1.0f - minPw); if (!halfPhase && phase >= pw) { float crossing = -(phase - pw) / deltaPhase; squareMinBLEP.jump(crossing, 2.0); halfPhase = true; } } // Reset phase if at end of cycle if (phase >= 1.0) { phase -= 1.0; float crossing = -phase / deltaPhase; squareMinBLEP.jump(crossing, -2.0); halfPhase = false; } float square = (phase < pw) ? -1.0 : 1.0; square += squareMinBLEP.shift(); TBase::outputs[SQUARE_OUTPUT].value = 5.0*square; } template inline void EvenVCO::step() { // We don't need to look for connected outputs every cycle. // do it less often, and store results. if (--loopCounter < 0) { loopCounter = 16; doSaw = TBase::outputs[SAW_OUTPUT].active; doEven = TBase::outputs[EVEN_OUTPUT].active; doTri = TBase::outputs[TRI_OUTPUT].active; doSq = TBase::outputs[SQUARE_OUTPUT].active; doSin = TBase::outputs[SINE_OUTPUT].active; if (doSaw && !doEven && !doTri && !doSq && !doSin) { dispatcher = SAW_OUTPUT; zeroOutputsExcept(SAW_OUTPUT); } else if (!doSaw && doEven && !doTri && !doSq) { dispatcher = EVEN_OUTPUT; zeroOutputsExcept(EVEN_OUTPUT); } else if (!doSaw && !doEven && !doTri && !doSq && doSin) { dispatcher = SINE_OUTPUT; zeroOutputsExcept(SINE_OUTPUT); } else if (!doSaw && !doEven && doTri && !doSq && !doSin) { dispatcher = TRI_OUTPUT; zeroOutputsExcept(TRI_OUTPUT); } else if (!doSaw && !doEven && !doTri && doSq && !doSin) { dispatcher = SQUARE_OUTPUT; zeroOutputsExcept(SQUARE_OUTPUT); } else { dispatcher = NUM_OUTPUTS; } } // Compute frequency, pitch is 1V/oct float pitch = 1.0 + roundf(TBase::params[OCTAVE_PARAM].value) + TBase::params[TUNE_PARAM].value / 12.0; pitch += TBase::inputs[PITCH1_INPUT].value + TBase::inputs[PITCH2_INPUT].value; pitch += TBase::inputs[FM_INPUT].value / 4.0; #if 1 // Use lookup table for pitch lookup const float q = float(log2(261.626)); // move up to pitch range of EvenVCO pitch += q; _freq = expLookup(pitch); #else _freq = 261.626 * powf(2.0, pitch); _freq = clamp(_freq, 0.0f, 20000.0f); #endif // Advance phase float f = (_testFreq) ? _testFreq : _freq; float deltaPhase = sq::clamp(f * TBase::engineGetSampleTime(), 1e-6f, 0.5f); // call the dedicated dispatch routines for the special case waveforms. switch (dispatcher) { case SAW_OUTPUT: step_saw(deltaPhase); break; case EVEN_OUTPUT: step_even(deltaPhase); break; case SINE_OUTPUT: step_sin(deltaPhase); break; case TRI_OUTPUT: step_tri(deltaPhase); break; case SQUARE_OUTPUT: step_sq(deltaPhase); break; case NUM_OUTPUTS: step_all(deltaPhase); break; default: assert(false); } } /** * Less optimized version that can do all waveform combinations */ template inline void EvenVCO::step_all(float deltaPhase) { float oldPhase = phase; phase += deltaPhase; if (oldPhase < 0.5 && phase >= 0.5) { const float crossing = -(phase - 0.5) / deltaPhase; if (doTri) { triSquareMinBLEP.jump(crossing, 2.0); } if (doEven) { doubleSawMinBLEP.jump(crossing, -2.0); } } // Pulse width float pw; if (doSq) { pw = TBase::params[PWM_PARAM].value + TBase::inputs[PWM_INPUT].value / 5.0; const float minPw = 0.05f; pw = sq::rescale(sq::clamp(pw, -1.0f, 1.0f), -1.0f, 1.0f, minPw, 1.0f - minPw); if (!halfPhase && phase >= pw) { const float crossing = -(phase - pw) / deltaPhase; squareMinBLEP.jump(crossing, 2.0); halfPhase = true; } } // Reset phase if at end of cycle if (phase >= 1.0) { phase -= 1.0; float crossing = -phase / deltaPhase; if (doTri) { triSquareMinBLEP.jump(crossing, -2.0); } if (doEven) { doubleSawMinBLEP.jump(crossing, -2.0); } if (doSq) { squareMinBLEP.jump(crossing, -2.0); } if (doSaw) { sawMinBLEP.jump(crossing, -2.0); } halfPhase = false; } // Outputs if (doTri) { float triSquare = (phase < 0.5) ? -1.0 : 1.0; triSquare += triSquareMinBLEP.shift(); // Integrate square for triangle tri += 4.0 * triSquare * _freq * TBase::engineGetSampleTime(); tri *= (1.0 - 40.0 * TBase::engineGetSampleTime()); } float sine = 0; float even = 0; float saw = 0; float square = 0; if (doSin || doEven) { //sine = -cosf(2*AudioMath::Pi * phase); // want cosine, but only have sine lookup float adjPhase = phase + .25f; if (adjPhase >= 1) { adjPhase -= 1; } sine = -LookupTable::lookup(*sinLookup, adjPhase, true); } if (doEven) { float doubleSaw = (phase < 0.5) ? (-1.0 + 4.0*phase) : (-1.0 + 4.0*(phase - 0.5)); doubleSaw += doubleSawMinBLEP.shift(); even = 0.55 * (doubleSaw + 1.27 * sine); } if (doSaw) { saw = -1.0 + 2.0*phase; saw += sawMinBLEP.shift(); } if (doSq) { square = (phase < pw) ? -1.0 : 1.0; square += squareMinBLEP.shift(); } else { TBase::outputs[SQUARE_OUTPUT].value = 0; } // Set outputs // get rid of redundant stuff here TBase::outputs[TRI_OUTPUT].value = doTri ? 5.0*tri : 0; TBase::outputs[SINE_OUTPUT].value = 5.0*sine; TBase::outputs[EVEN_OUTPUT].value = 5.0*even; TBase::outputs[SAW_OUTPUT].value = 5.0*saw; TBase::outputs[SQUARE_OUTPUT].value = 5.0*square; } #if defined(_MSC_VER) #pragma warning (pop) #endif