|
-
- #pragma once
-
- #include <vector>
-
- #include "ClockMult.h"
- #include "ObjectCache.h"
- #include "AsymRampShaper.h"
- #include "GateTrigger.h"
-
- /**
- */
- template <class TBase>
- class Tremolo : public TBase
- {
- public:
- Tremolo(struct Module * module) : TBase(module)
- {
- }
- Tremolo() : TBase()
- {
- }
- void setSampleRate(float rate)
- {
- reciprocalSampleRate = 1 / rate;
- }
-
- // must be called after setSampleRate
- void init();
-
- enum ParamIds
- {
- LFO_RATE_PARAM,
- LFO_SHAPE_PARAM,
- LFO_SKEW_PARAM,
- LFO_PHASE_PARAM,
- MOD_DEPTH_PARAM,
- CLOCK_MULT_PARAM,
-
- LFO_SHAPE_TRIM_PARAM,
- LFO_SKEW_TRIM_PARAM,
- LFO_PHASE_TRIM_PARAM,
- MOD_DEPTH_TRIM_PARAM,
- NUM_PARAMS
- };
-
- enum InputIds
- {
- AUDIO_INPUT,
- CLOCK_INPUT,
- LFO_SHAPE_INPUT,
- LFO_SKEW_INPUT,
- LFO_PHASE_INPUT,
- MOD_DEPTH_INPUT,
- NUM_INPUTS
- };
-
- enum OutputIds
- {
- AUDIO_OUTPUT,
- SAW_OUTPUT,
- LFO_OUTPUT,
- NUM_OUTPUTS
- };
-
- enum LightIds
- {
- NUM_LIGHTS
- };
-
- /**
- * Main processing entry point. Called every sample
- */
- void step();
-
- private:
-
- ClockMult clock;
- std::shared_ptr<LookupTableParams<float>> tanhLookup;
- float reciprocalSampleRate = 0;
-
- AsymRampShaperParams rampShaper;
- std::shared_ptr<LookupTableParams<float>> exp2 = ObjectCache<float>::getExp2();
-
- // make some bootstrap scalers
- AudioMath::ScaleFun<float> scale_rate;
- AudioMath::ScaleFun<float> scale_skew;
- AudioMath::ScaleFun<float> scale_shape;
- AudioMath::ScaleFun<float> scale_depth;
- AudioMath::ScaleFun<float> scale_phase;
-
- GateTrigger gateTrigger;
- };
-
-
-
- template <class TBase>
- inline void Tremolo<TBase>::init()
- {
- tanhLookup = ObjectCache<float>::getTanh5();
- clock.setMultiplier(0);
-
- scale_rate = AudioMath::makeLinearScaler(4.f, 9.f); // log domain, 16 range
- scale_skew = AudioMath::makeLinearScaler(-.99f, .99f);
- scale_shape = AudioMath::makeLinearScaler(0.f, 1.f);
- scale_depth = AudioMath::makeLinearScaler(0.f, 1.f);
- scale_phase = AudioMath::makeLinearScaler(-1.f, 1.f);
- }
-
- template <class TBase>
- inline void Tremolo<TBase>::step()
- {
- gateTrigger.go(TBase::inputs[CLOCK_INPUT].value);
- if (gateTrigger.trigger()) {
- clock.refClock();
- }
-
- int clockMul = (int) round(TBase::params[CLOCK_MULT_PARAM].value);
-
- // UI is shifted
- clockMul++;
- if (clockMul > 4) {
- clockMul = 0;
- }
-
-
-
- clock.setMultiplier(clockMul);
-
-
- const float shape = scale_shape(
- TBase::inputs[LFO_SHAPE_INPUT].value,
- TBase::params[LFO_SHAPE_PARAM].value,
- TBase::params[LFO_SHAPE_TRIM_PARAM].value);
-
- const float skew = scale_skew(
- TBase::inputs[LFO_SKEW_INPUT].value,
- TBase::params[LFO_SKEW_PARAM].value,
- TBase::params[LFO_SKEW_TRIM_PARAM].value);
-
- const float phase = scale_phase(
- TBase::inputs[LFO_PHASE_INPUT].value,
- TBase::params[LFO_PHASE_PARAM].value,
- TBase::params[LFO_PHASE_TRIM_PARAM].value);
-
- const float modDepth = scale_depth(
- TBase::inputs[MOD_DEPTH_INPUT].value,
- TBase::params[MOD_DEPTH_PARAM].value,
- TBase::params[MOD_DEPTH_TRIM_PARAM].value);
-
- if (clockMul == 0) // only calc rate for internal
- {
- const float logRate = scale_rate(
- 0,
- TBase::params[LFO_RATE_PARAM].value,
- 1);
-
- float rate = LookupTable<float>::lookup(*exp2, logRate);
- float scaledRate = rate * .06f;
- clock.setFreeRunFreq(scaledRate * reciprocalSampleRate);
- }
-
-
-
- // For now, call setup every sample. will eat a lot of cpu
- AsymRampShaper::setup(rampShaper, skew, phase);
-
- // ------------ now generate the lfo waveform
- clock.sampleClock();
- float mod = clock.getSaw();
- mod = AsymRampShaper::proc_1(rampShaper, mod);
- mod -= 0.5f;
- // now we have a skewed saw -.5 to .5
- TBase::outputs[SAW_OUTPUT].value = mod;
-
- // TODO: don't scale twice - just get it right the first tme
- const float shapeMul = std::max(.25f, 10 * shape);
- mod *= shapeMul;
-
- mod = LookupTable<float>::lookup(*tanhLookup.get(), mod);
- TBase::outputs[LFO_OUTPUT].value = mod;
-
- const float gain = modDepth /
- LookupTable<float>::lookup(*tanhLookup.get(), (shapeMul / 2));
- const float finalMod = gain * mod + 1; // TODO: this offset by 1 is pretty good, but we
- // could add an offset control to make it really "chop" off
-
- TBase::outputs[AUDIO_OUTPUT].value = TBase::inputs[AUDIO_INPUT].value * finalMod;
- }
-
- /*
-
-
- old plug proc loop.
-
- // Step 1: generate a saw
- // range is 0..1
- SawOsc<vec_t>::gen_v(*sawState, *sawParams, tempBuffer, sampleFrames);
-
- // step 2: apply skew and phase shift
- // range still 0..1
- AsymRampShaper<vec_t>::proc_v(*shaperParams, tempBuffer, tempBuffer, sampleFrames);
-
- // step 3: shift down to be centered at zero,
- // max excursion +-5 at shape "most square"
- // min is +/- .25 TODO: put the .25 into the control range itself
-
- // range = +/- (5 * shape)
- //
- f_t shapeMul = std::max(.25, 10 * controlValues.lfoShape);
- VecBasic<vec_t>::add_mul_c_imp(tempBuffer, sampleFrames, shapeMul, -.5f);
-
- // now tanh,
- // output contered around zero,
- // max is tanh(.25) to tanh(5), depending on shape value
- // rang = +/- tanh(5 * shape)
- LookupUniform<vec_t>::lookup_clip_v(*tanhParams, tempBuffer, tempBuffer, sampleFrames);
-
- // so: makeup gain of 1/tanh(shapeMul) will get us to +1/-1
- // then multiply by depth to get contered around zero with correct depth
- // the add one to get back to trem range!
- f_t gain = controlValues.modDepth / tanh(shapeMul/2);
- VecBasic<vec_t>::mul_add_c_imp(tempBuffer, sampleFrames, gain, 1);
- // scale then add constant
- // input = a * input + b
- static void mul_add_c_imp(f_t * inout, int size, f_t a, f_t b) {
- assert_size(size);
-
- // now range = +/- tanh(5*shape) * depth / tanh(10 * shape)
- */
|