diff --git a/src/Octaves.cpp b/src/Octaves.cpp index 33f53e3..52f7af5 100644 --- a/src/Octaves.cpp +++ b/src/Octaves.cpp @@ -18,13 +18,42 @@ float aliasSuppressedOffsetSaw(const float* phases, float pw) { for (int i = 0; i < 3; ++i) { float pwp = 2 * phases[i] - 2 * pw; // range -1 to +1 - + pwp += simd::ifelse(pwp > 1, -2, 0); // modulo on [-1, +1] sawOffsetBuff[i] = (pwp * pwp * pwp - pwp) / 6.0; // eq 11 } return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]); } +template +class HardClipperADAA { +public: + + T process(T x) { + T y = simd::ifelse(simd::abs(x - xPrev) < 1e-5, + f(0.5 * (xPrev + x)), + (F(x) - F(xPrev)) / (x - xPrev)); + + xPrev = x; + return y; + } + + + static T f(T x) { + return simd::ifelse(simd::abs(x) < 1, x, simd::sgn(x)); + } + + static T F(T x) { + return simd::ifelse(simd::abs(x) < 1, 0.5 * x * x, x * simd::sgn(x) - 0.5); + } + + void reset() { + xPrev = 0.f; + } + +private: + T xPrev = 0.f; +}; struct Octaves : Module { enum ParamId { @@ -77,7 +106,9 @@ struct Octaves : Module { bool limitPW = true; bool removePulseDC = false; + bool adaa = false; int oversamplingIndex = 0; + static const int NUM_OUTPUTS = 6; Octaves() { config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); @@ -122,6 +153,8 @@ struct Octaves : Module { float phases[3]; bool forceNaive = false; + HardClipperADAA hardClipper[NUM_OUTPUTS]; + void process(const ProcessArgs& args) override { float pitch = params[TUNE_PARAM].getValue() + inputs[VOCT1_INPUT].getVoltage() + inputs[VOCT2_INPUT].getVoltage(); @@ -130,7 +163,7 @@ struct Octaves : Module { // -1 to +1 float pwmCV = params[PWM_CV_PARAM].getValue() * clamp(inputs[PWM_INPUT].getVoltage() / 10.f, -1.f, 1.f); const float pulseWidthLimit = limitPW ? 0.05f : 0.0f; - + // pwm in [-0.25 : +0.25] float pwm = clamp(0.5 - params[PWM_PARAM].getValue() + 0.5 * pwmCV, -0.5f + pulseWidthLimit, 0.5f - pulseWidthLimit); pwm /= 2.0; @@ -142,11 +175,11 @@ struct Octaves : Module { float sum = 0.f; float sumNaive = 0.f; - for (int c = 0; c < 6; c++) { + for (int c = 0; c < NUM_OUTPUTS; c++) { // derive phases for higher octaves from base phase (this keeps things in sync!) const float n = (float)(1 << c); // this is on [0, 1] - const float effectivePhaseRaw = n * std::fmod(phase, 1 / n); + const float effectivePhaseRaw = n * std::fmod(phase, 1 / n); // this is on [0, 1], and offset in time by 0.25 const float effectivePhase = std::fmod(effectivePhaseRaw + 0.25, 1); @@ -194,22 +227,24 @@ struct Octaves : Module { sum += outForOctave; - sum = clamp(sum, -1.f, 1.f); + if (adaa) { + sum = hardClipper[c].process(sum); + } + else { + sum = clamp(sum, -1.f, 1.f); + } if (outputs[OUT_01F_OUTPUT + c].isConnected()) { outputs[OUT_01F_OUTPUT + c].setVoltage(5 * sum); sum = 0.f; } - if (c == 0) { - outputs[OUT_OUTPUT].setVoltage(effectivePhase); - - float saw = aliasSuppressedSaw(phases, 2*pwm); - float sawOffset = aliasSuppressedOffsetSaw(phases, 2*pwm); - float denominatorInv = 0.25 / (effectiveDeltaPhase * effectiveDeltaPhase); - float dpwOrder3_ = gain * (-saw) * denominatorInv; + if (false) { + float x = 3 * std::sin(2 * M_PI * effectivePhase); + outputs[OUT_OUTPUT].setVoltage(clamp(x, -1.f, 1.f)); - outputs[OUT2_OUTPUT].setVoltage(dpwOrder3_); + //float y = hardClipper.process(x); + //outputs[OUT2_OUTPUT].setVoltage(y); } @@ -226,6 +261,7 @@ struct Octaves : Module { json_object_set_new(rootJ, "removePulseDC", json_boolean(removePulseDC)); json_object_set_new(rootJ, "limitPW", json_boolean(limitPW)); json_object_set_new(rootJ, "forceNaive", json_boolean(forceNaive)); + json_object_set_new(rootJ, "adaa", json_boolean(adaa)); // TODO: // json_object_set_new(rootJ, "oversamplingIndex", json_integer(oversampler[0].getOversamplingIndex())); return rootJ; @@ -253,6 +289,11 @@ struct Octaves : Module { oversamplingIndex = json_integer_value(oversamplingIndexJ); onSampleRateChange(); } + + json_t* adaaJ = json_object_get(rootJ, "adaa"); + if (adaaJ) { + adaa = json_boolean_value(adaaJ); + } } }; @@ -336,6 +377,8 @@ struct OctavesWidget : ModuleWidget { menu->addChild(createBoolPtrMenuItem("Force naive waveforms", "", &module->forceNaive)); + menu->addChild(createBoolPtrMenuItem("ADAADAA", "", &module->adaa)); + } };