#pragma once #include "LookupTable.h" #include "SinOscillator.h" #include "BiquadFilter.h" #include "BiquadParams.h" #include "BiquadState.h" #include "HilbertFilterDesigner.h" /** * Complete Frequency Shifter composite * * If TBase is WidgetComposite, this class is used as the implementation part of the Booty Shifter module. * If TBase is TestComposite, this class may stand alone for unit tests. */ template class FrequencyShifter : public TBase { public: FrequencyShifter(struct Module * module) : TBase(module) { } FrequencyShifter() : TBase() { } void setSampleRate(float rate) { reciprocalSampleRate = 1 / rate; HilbertFilterDesigner::design(rate, hilbertFilterParamsSin, hilbertFilterParamsCos); } // must be called after setSampleRate void init() { SinOscillator::setFrequency(oscParams, T(.01)); exponential2 = ObjectCache::getExp2(); // Get a shared copy of the 2**x lookup. // This will enable exp mode to track at // 1V/ octave. } // Define all the enums here. This will let the tests and the widget access them. enum ParamIds { PITCH_PARAM, // the big pitch knob NUM_PARAMS }; enum InputIds { AUDIO_INPUT, CV_INPUT, NUM_INPUTS }; enum OutputIds { SIN_OUTPUT, COS_OUTPUT, NUM_OUTPUTS }; enum LightIds { NUM_LIGHTS }; /** * Main processing entry point. Called every sample */ void step(); typedef float T; // use floats for all signals T freqRange = 5; // the freq range switch private: SinOscillatorParams oscParams; SinOscillatorState oscState; BiquadParams hilbertFilterParamsSin; BiquadParams hilbertFilterParamsCos; BiquadState hilbertFilterStateSin; BiquadState hilbertFilterStateCos; std::shared_ptr> exponential2; float reciprocalSampleRate; }; template inline void FrequencyShifter::step() { assert(exponential2->isValid()); // Add the knob and the CV value. T freqHz; T cvTotal = TBase::params[PITCH_PARAM].value + TBase::inputs[CV_INPUT].value; if (cvTotal > 5) { cvTotal = 5; } if (cvTotal < -5) { cvTotal = -5; } if (freqRange > .2) { cvTotal *= freqRange; cvTotal *= T(1. / 5.); freqHz = cvTotal; } else { cvTotal += 7; // shift up to GE 2 (min value for out 1v/oct lookup) freqHz = LookupTable::lookup(*exponential2, cvTotal); freqHz /= 2; // down to 2..2k range that we want. } SinOscillator::setFrequency(oscParams, freqHz * reciprocalSampleRate); // Generate the quadrature sin oscillators. T x, y; SinOscillator::runQuadrature(x, y, oscState, oscParams); // Filter the input through th quadrature filter const T input = TBase::inputs[AUDIO_INPUT].value; const T hilbertSin = BiquadFilter::run(input, hilbertFilterStateSin, hilbertFilterParamsSin); const T hilbertCos = BiquadFilter::run(input, hilbertFilterStateCos, hilbertFilterParamsCos); // Cross modulate the two sections. x *= hilbertSin; y *= hilbertCos; // And combine for final SSB output. TBase::outputs[SIN_OUTPUT].value = x + y; TBase::outputs[COS_OUTPUT].value = x - y; }