| @@ -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<typename T> | |||
| 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<float> 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)); | |||
| } | |||
| }; | |||