From 4ddef43a00bbe8260e1c8a50022ae78effafa326 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sat, 6 Nov 2021 17:03:59 -0400 Subject: [PATCH] Implement PROB and RND parameters in Random. --- src/Random.cpp | 195 +++++++++++++++++++++++++------------------------ 1 file changed, 100 insertions(+), 95 deletions(-) diff --git a/src/Random.cpp b/src/Random.cpp index 023442f..afd91ea 100644 --- a/src/Random.cpp +++ b/src/Random.cpp @@ -8,8 +8,8 @@ struct Random : Module { OFFSET_PARAM, MODE_PARAM, // new in 2.0 - PROB_PARAM, // TODO - RAND_PARAM, // TODO + PROB_PARAM, + RAND_PARAM, RATE_CV_PARAM, SHAPE_CV_PARAM, PROB_CV_PARAM, @@ -19,7 +19,7 @@ struct Random : Module { enum InputIds { RATE_INPUT, SHAPE_INPUT, - TRIGGER_INPUT, + TRIG_INPUT, EXTERNAL_INPUT, // new in 2.0 PROB_INPUT, @@ -32,7 +32,7 @@ struct Random : Module { SMOOTH_OUTPUT, EXPONENTIAL_OUTPUT, // new in 2.0 - GATE_OUTPUT, // TODO + TRIG_OUTPUT, NUM_OUTPUTS }; enum LightIds { @@ -44,101 +44,112 @@ struct Random : Module { NUM_LIGHTS }; - dsp::SchmittTrigger trigTrigger; - dsp::BooleanTrigger offsetTrigger; - float lastValue = 0.f; - float value = 0.f; + float lastVoltage = 0.f; + float nextVoltage = 0.f; + /** Waits at 1 until reset */ + float phase = 0.f; + float clockFreq = 0.f; + /** Internal clock phase */ float clockPhase = 0.f; - int trigFrame = 0; - int lastTrigFrames = INT_MAX; + /** External clock timer */ + dsp::Timer clockTimer; + dsp::SchmittTrigger clockTrigger; + dsp::PulseGenerator trigGenerator; Random() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); - configParam(RATE_PARAM, std::log2(0.002f), std::log2(2000.f), std::log2(2.f), "Rate", " Hz", 2); + configParam(RATE_PARAM, std::log2(0.002f), std::log2(2000.f), std::log2(2.f), "Clock rate", " Hz", 2); configParam(SHAPE_PARAM, 0.f, 1.f, 1.f, "Shape", "%", 0, 100); - configParam(PROB_PARAM, 0.f, 1.f, 1.f, "Probability", "%", 0, 100); - configParam(RAND_PARAM, 0.f, 1.f, 1.f, "Randomness", "%", 0, 100); + configParam(PROB_PARAM, 0.f, 1.f, 1.f, "Trigger probability", "%", 0, 100); + configParam(RAND_PARAM, 0.f, 1.f, 1.f, "Random spread", "%", 0, 100); - configParam(RATE_CV_PARAM, -1.f, 1.f, 0.f, "Rate CV", "%", 0, 100); + configParam(RATE_CV_PARAM, -1.f, 1.f, 0.f, "Clock rate CV", "%", 0, 100); getParamQuantity(RATE_CV_PARAM)->randomizeEnabled = false; configParam(SHAPE_CV_PARAM, -1.f, 1.f, 0.f, "Shape CV", "%", 0, 100); getParamQuantity(SHAPE_CV_PARAM)->randomizeEnabled = false; - configParam(PROB_CV_PARAM, -1.f, 1.f, 0.f, "Probability CV", "%", 0, 100); + configParam(PROB_CV_PARAM, -1.f, 1.f, 0.f, "Trigger probability CV", "%", 0, 100); getParamQuantity(PROB_CV_PARAM)->randomizeEnabled = false; - configParam(RAND_CV_PARAM, -1.f, 1.f, 0.f, "Randomness CV", "%", 0, 100); + configParam(RAND_CV_PARAM, -1.f, 1.f, 0.f, "Random spread CV", "%", 0, 100); getParamQuantity(RAND_CV_PARAM)->randomizeEnabled = false; configSwitch(OFFSET_PARAM, 0.f, 1.f, 0.f, "Offset", {"Bipolar", "Unipolar"}); - configInput(RATE_INPUT, "Rate"); + configInput(RATE_INPUT, "Clock rate"); configInput(SHAPE_INPUT, "Shape"); - configInput(PROB_INPUT, "Probability"); - configInput(RAND_INPUT, "Randomness"); - configInput(TRIGGER_INPUT, "Trigger"); + configInput(PROB_INPUT, "Trigger probability"); + configInput(RAND_INPUT, "Random spread"); + configInput(TRIG_INPUT, "Trigger"); configInput(EXTERNAL_INPUT, "External"); configOutput(STEPPED_OUTPUT, "Stepped"); configOutput(LINEAR_OUTPUT, "Linear"); configOutput(SMOOTH_OUTPUT, "Smooth"); configOutput(EXPONENTIAL_OUTPUT, "Exponential"); - configOutput(GATE_OUTPUT, "Gate"); + configOutput(TRIG_OUTPUT, "Trigger"); } - void trigger() { - lastValue = value; - if (inputs[EXTERNAL_INPUT].isConnected()) { - value = inputs[EXTERNAL_INPUT].getVoltage() / 10.f; - } - else { - // Choose a new random value - bool absolute = params[MODE_PARAM].getValue() > 0.f; - bool uni = params[OFFSET_PARAM].getValue() > 0.f; - if (absolute) { - value = random::uniform(); - if (!uni) - value -= 0.5f; + void process(const ProcessArgs& args) override { + // Params + float shape = params[SHAPE_PARAM].getValue(); + shape += inputs[SHAPE_INPUT].getVoltage() / 10.f * params[SHAPE_CV_PARAM].getValue(); + shape = clamp(shape, 0.f, 1.f); + + float rand = params[RAND_PARAM].getValue(); + rand += inputs[RAND_INPUT].getVoltage() / 10.f * params[RAND_CV_PARAM].getValue(); + rand = clamp(rand, 0.f, 1.f); + + bool uni = params[OFFSET_PARAM].getValue() > 0.f; + + auto trigger = [&]() { + float prob = params[PROB_PARAM].getValue(); + prob += inputs[PROB_INPUT].getVoltage() / 10.f * params[PROB_CV_PARAM].getValue(); + prob = clamp(prob, 0.f, 1.f); + + lights[RATE_LIGHT].setBrightness(3.f); + + // Probabilistic trigger + if (prob < 1.f && random::uniform() > prob) + return; + + // Generate next random voltage + lastVoltage = nextVoltage; + if (inputs[EXTERNAL_INPUT].isConnected()) { + nextVoltage = inputs[EXTERNAL_INPUT].getVoltage(); } else { - // Switch to uni if bi - if (!uni) - value += 0.5f; - float deltaValue = random::normal(); - // Bias based on value - deltaValue -= (value - 0.5f) * 2.f; - // Scale delta and accumulate value - const float stdDev = 1 / 10.f; - deltaValue *= stdDev; - value += deltaValue; - value = clamp(value, 0.f, 1.f); - // Switch back to bi + // Crossfade new random voltage with last + float v = 10.f * random::uniform(); if (!uni) - value -= 0.5f; + v -= 5.f; + nextVoltage = crossfade(nextVoltage, v, rand); } - } - lights[RATE_LIGHT].setBrightness(3.f); - } - void process(const ProcessArgs& args) override { - if (inputs[TRIGGER_INPUT].isConnected()) { - // Advance clock phase based on tempo estimate - trigFrame++; - float deltaPhase = 1.f / lastTrigFrames; - clockPhase += deltaPhase; - clockPhase = std::min(clockPhase, 1.f); - // Trigger - if (trigTrigger.process(rescale(inputs[TRIGGER_INPUT].getVoltage(), 0.1f, 2.f, 0.f, 1.f))) { - clockPhase = 0.f; - lastTrigFrames = trigFrame; - trigFrame = 0; + phase = 0.f; + + trigGenerator.trigger(1e-3f); + lights[PROB_LIGHT].setBrightness(3.f); + }; + + // Trigger or internal clock + float deltaPhase; + + if (inputs[TRIG_INPUT].isConnected()) { + clockTimer.process(args.sampleTime); + + if (clockTrigger.process(inputs[TRIG_INPUT].getVoltage(), 0.1f, 2.f)) { + clockFreq = 1.f / clockTimer.getTime(); + clockTimer.reset(); trigger(); } + + deltaPhase = std::fmin(clockFreq * args.sampleTime, 0.5f); } else { // Advance clock phase by rate float rate = params[RATE_PARAM].getValue(); rate += inputs[RATE_PARAM].getVoltage() * params[RATE_CV_PARAM].getValue(); - float clockFreq = std::pow(2.f, rate); - float deltaPhase = std::fmin(clockFreq * args.sampleTime, 0.5f); + clockFreq = std::pow(2.f, rate); + deltaPhase = std::fmin(clockFreq * args.sampleTime, 0.5f); clockPhase += deltaPhase; // Trigger if (clockPhase >= 1.f) { @@ -147,25 +158,16 @@ struct Random : Module { } } - // Params - float shape = params[SHAPE_PARAM].getValue(); - shape += inputs[SHAPE_INPUT].getVoltage() / 10.f * params[SHAPE_CV_PARAM].getValue(); - shape = clamp(shape, 0.f, 1.f); - - float prob = params[PROB_PARAM].getValue(); - prob += inputs[PROB_INPUT].getVoltage() / 10.f * params[PROB_CV_PARAM].getValue(); - prob = clamp(prob, 0.f, 1.f); - - float rand = params[RAND_PARAM].getValue(); - rand += inputs[RAND_INPUT].getVoltage() / 10.f * params[RAND_CV_PARAM].getValue(); - rand = clamp(rand, 0.f, 1.f); + // Advance phase + phase += deltaPhase; + phase = std::min(1.f, phase); // Stepped if (outputs[STEPPED_OUTPUT].isConnected()) { float steps = std::ceil(std::pow(shape, 2) * 15 + 1); - float v = std::ceil(clockPhase * steps) / steps; - v = rescale(v, 0.f, 1.f, lastValue, value); - outputs[STEPPED_OUTPUT].setVoltage(v * 10.f); + float v = std::ceil(phase * steps) / steps; + v = rescale(v, 0.f, 1.f, lastVoltage, nextVoltage); + outputs[STEPPED_OUTPUT].setVoltage(v); } // Linear @@ -173,13 +175,13 @@ struct Random : Module { float slope = 1 / shape; float v; if (slope < 1e6f) { - v = std::fmin(clockPhase * slope, 1.f); + v = std::fmin(phase * slope, 1.f); } else { v = 1.f; } - v = rescale(v, 0.f, 1.f, lastValue, value); - outputs[LINEAR_OUTPUT].setVoltage(v * 10.f); + v = rescale(v, 0.f, 1.f, lastVoltage, nextVoltage); + outputs[LINEAR_OUTPUT].setVoltage(v); } // Smooth @@ -187,39 +189,42 @@ struct Random : Module { float p = 1 / shape; float v; if (p < 1e6f) { - v = std::fmin(clockPhase * p, 1.f); + v = std::fmin(phase * p, 1.f); v = std::cos(M_PI * v); } else { v = -1.f; } - v = rescale(v, 1.f, -1.f, lastValue, value); - outputs[SMOOTH_OUTPUT].setVoltage(v * 10.f); + v = rescale(v, 1.f, -1.f, lastVoltage, nextVoltage); + outputs[SMOOTH_OUTPUT].setVoltage(v); } // Exp if (outputs[EXPONENTIAL_OUTPUT].isConnected()) { - float b = std::pow(shape, 4); + float b = std::pow(shape, 8); float v; if (0.999f < b) { - v = clockPhase; + v = phase; } else if (1e-20f < b) { - v = (std::pow(b, clockPhase) - 1.f) / (b - 1.f); + v = (std::pow(b, phase) - 1.f) / (b - 1.f); } else { v = 1.f; } - v = rescale(v, 0.f, 1.f, lastValue, value); - outputs[EXPONENTIAL_OUTPUT].setVoltage(v * 10.f); + v = rescale(v, 0.f, 1.f, lastVoltage, nextVoltage); + outputs[EXPONENTIAL_OUTPUT].setVoltage(v); } + // Trigger output + outputs[TRIG_OUTPUT].setVoltage(trigGenerator.process(args.sampleTime) ? 0.f : 10.f); + // Lights lights[RATE_LIGHT].setSmoothBrightness(0.f, args.sampleTime); - lights[SHAPE_LIGHT].setBrightness(shape); - lights[PROB_LIGHT].setBrightness(prob); + lights[PROB_LIGHT].setSmoothBrightness(0.f, args.sampleTime); lights[RAND_LIGHT].setBrightness(rand); - // lights[OFFSET_LIGHT].setBrightness(uni); + lights[SHAPE_LIGHT].setBrightness(shape); + lights[OFFSET_LIGHT].setBrightness(uni); } void paramsFromJson(json_t* rootJ) override { @@ -257,10 +262,10 @@ struct RandomWidget : ModuleWidget { addInput(createInputCentered(mm2px(Vec(17.317, 80.549)), module, Random::PROB_INPUT)); addInput(createInputCentered(mm2px(Vec(28.154, 80.553)), module, Random::RAND_INPUT)); addInput(createInputCentered(mm2px(Vec(38.991, 80.557)), module, Random::SHAPE_INPUT)); - addInput(createInputCentered(mm2px(Vec(6.479, 96.859)), module, Random::TRIGGER_INPUT)); + addInput(createInputCentered(mm2px(Vec(6.479, 96.859)), module, Random::TRIG_INPUT)); addInput(createInputCentered(mm2px(Vec(17.317, 96.859)), module, Random::EXTERNAL_INPUT)); - addOutput(createOutputCentered(mm2px(Vec(38.991, 96.859)), module, Random::GATE_OUTPUT)); + addOutput(createOutputCentered(mm2px(Vec(38.991, 96.859)), module, Random::TRIG_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(6.479, 113.115)), module, Random::STEPPED_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(17.317, 113.115)), module, Random::LINEAR_OUTPUT)); addOutput(createOutputCentered(mm2px(Vec(28.154, 113.115)), module, Random::EXPONENTIAL_OUTPUT));