#include #include "FrozenWasteland.hpp" #include "dsp/digital.hpp" #define BUFFER_SIZE 512 namespace rack_plugin_FrozenWasteland { struct RouletteLFO : Module { enum ParamIds { FIXED_RADIUS_PARAM, ROTATING_RADIUS_PARAM, DISTANCE_PARAM, FREQUENCY_PARAM, EPI_HYPO_PARAM, FIXED_D_PARAM, NUM_PARAMS }; enum InputIds { FIXED_RADIUS_INPUT, ROATATING_RADIUS_INPUT, DISTANCE_INPUT, FREQUENCY_INPUT, NUM_INPUTS }; enum OutputIds { OUTPUT_X, OUTPUT_Y, NUM_OUTPUTS }; enum RouletteTypes { HYPOTROCHOID_ROULETTE, EPITROCHIID_ROULETTE }; float bufferX1[BUFFER_SIZE] = {}; float bufferY1[BUFFER_SIZE] = {}; int bufferIndex = 0; float frameIndex = 0; float scopeDeltaTime = powf(2.0, -8); //SchmittTrigger resetTrigger; float x1 = 0.0; float y1 = 0.0; float phase = 0.0; RouletteLFO() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} void step() override; // For more advanced Module features, read Rack's engine.hpp header file // - toJson, fromJson: serialization of internal data // - onSampleRateChange: event triggered by a change of sample rate // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu }; void RouletteLFO::step() { float pitch = fminf(params[FREQUENCY_PARAM].value + inputs[FREQUENCY_INPUT].value, 8.0); float freq = powf(2.0, pitch); float deltaTime = 1.0 / engineGetSampleRate(); float deltaPhase = fminf(freq * deltaTime, 0.5); phase += deltaPhase; if (phase >= 1.0) phase -= 1.0; if(params[EPI_HYPO_PARAM].value == HYPOTROCHOID_ROULETTE) { float r = clamp(params[ROTATING_RADIUS_PARAM].value + inputs[ROATATING_RADIUS_INPUT].value,1.0,10.0); float R = clamp(params[FIXED_RADIUS_PARAM].value +inputs[FIXED_RADIUS_INPUT].value,r,20.0); float d = clamp(params[DISTANCE_PARAM].value + inputs[DISTANCE_INPUT].value,1.0,10.0); if(params[FIXED_D_PARAM].value) { d=r; } float amplitudeScaling = 5.0 / (R-r+d); float theta = phase * 2 * M_PI; x1 = amplitudeScaling * (((R-r) * cosf(theta)) + (d * cosf((R-r)/r * theta))); y1 = amplitudeScaling * (((R-r) * sinf(theta)) - (d * sinf((R-r)/r * theta))); } else { float R = clamp(params[FIXED_RADIUS_PARAM].value +inputs[FIXED_RADIUS_INPUT].value,1.0,20.0); float r = clamp(params[ROTATING_RADIUS_PARAM].value + inputs[ROATATING_RADIUS_INPUT].value,1.0,10.0); float d = clamp(params[DISTANCE_PARAM].value + inputs[DISTANCE_INPUT].value,1.0,20.0); if(params[FIXED_D_PARAM].value) { d=r; } float amplitudeScaling = 5.0 / (R+r+d); float theta = phase * 2 * M_PI; x1 = amplitudeScaling * (((R+r) * cosf(theta)) - (d * cosf((R+r)/r * theta))); y1 = amplitudeScaling * (((R+r) * sinf(theta)) - (d * sinf((R+r)/r * theta))); } outputs[OUTPUT_X].value = x1; outputs[OUTPUT_Y].value = y1; //Update scope. int frameCount = (int)ceilf(scopeDeltaTime * engineGetSampleRate()); // Add frame to buffers if (bufferIndex < BUFFER_SIZE) { if (++frameIndex > frameCount) { frameIndex = 0; bufferX1[bufferIndex] = x1; bufferY1[bufferIndex] = y1; bufferIndex++; } } // Are we waiting on the next trigger? if (bufferIndex >= BUFFER_SIZE) { bufferIndex = 0; frameIndex = 0; } } struct RouletteScopeDisplay : TransparentWidget { RouletteLFO *module; int frame = 0; std::shared_ptr font; RouletteScopeDisplay() { } void drawWaveform(NVGcontext *vg, float *valuesX, float *valuesY) { if (!valuesX) return; nvgSave(vg); Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15*2))); nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y); nvgBeginPath(vg); // Draw maximum display left to right for (int i = 0; i < BUFFER_SIZE; i++) { float x, y; if (valuesY) { x = valuesX[i] / 2.0 + 0.5; y = valuesY[i] / 2.0 + 0.5; } else { x = (float)i / (BUFFER_SIZE - 1); y = valuesX[i] / 2.0 + 0.5; } Vec p; p.x = b.pos.x + b.size.x * x; p.y = b.pos.y + b.size.y * (1.0 - y); if (i == 0) nvgMoveTo(vg, p.x, p.y); else nvgLineTo(vg, p.x, p.y); } nvgLineCap(vg, NVG_ROUND); nvgMiterLimit(vg, 2.0); nvgStrokeWidth(vg, 1.5); nvgGlobalCompositeOperation(vg, NVG_LIGHTER); nvgStroke(vg); nvgResetScissor(vg); nvgRestore(vg); } void draw(NVGcontext *vg) override { //float gainX = powf(2.0, 1); //float gainY = powf(2.0, 1); //float offsetX = module->x1; //float offsetY = module->y1; float valuesX[BUFFER_SIZE]; float valuesY[BUFFER_SIZE]; for (int i = 0; i < BUFFER_SIZE; i++) { int j = i; // Lock display to buffer if buffer update deltaTime <= 2^-11 j = (i + module->bufferIndex) % BUFFER_SIZE; valuesX[i] = (module->bufferX1[j]) / 5.0; valuesY[i] = (module->bufferY1[j]) / 5.0; } // Draw waveforms for LFO 1 // X x Y nvgStrokeColor(vg, nvgRGBA(0x9f, 0xe4, 0x36, 0xc0)); drawWaveform(vg, valuesX, valuesY); } }; struct RouletteLFOWidget : ModuleWidget { RouletteLFOWidget(RouletteLFO *module); }; RouletteLFOWidget::RouletteLFOWidget(RouletteLFO *module) : ModuleWidget(module) { box.size = Vec(15*13, RACK_GRID_HEIGHT); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/RouletteLFO.svg"))); addChild(panel); } addChild(Widget::create(Vec(RACK_GRID_WIDTH - 12, 0))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, 0))); addChild(Widget::create(Vec(RACK_GRID_WIDTH-12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH + 12, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); { RouletteScopeDisplay *display = new RouletteScopeDisplay(); display->module = module; display->box.pos = Vec(0, 35); display->box.size = Vec(box.size.x, 140); addChild(display); } addParam(ParamWidget::create(Vec(10, 186), module, RouletteLFO::FIXED_RADIUS_PARAM, 1, 20.0, 5)); addParam(ParamWidget::create(Vec(60, 186), module, RouletteLFO::ROTATING_RADIUS_PARAM, 1, 10.0, 3)); addParam(ParamWidget::create(Vec(113, 186), module, RouletteLFO::DISTANCE_PARAM, 1, 10.0, 5.0)); addParam(ParamWidget::create(Vec(160, 186), module, RouletteLFO::FREQUENCY_PARAM, -8.0, 4.0, 0.0)); addParam(ParamWidget::create(Vec(55, 265), module, RouletteLFO::EPI_HYPO_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(130, 265), module, RouletteLFO::FIXED_D_PARAM, 0.0, 1.0, 0.0)); //addParam(ParamWidget::create(Vec(87, 265), module, RouletteLFO::FREQX2_PARAM, -8.0, 3.0, 0.0)); //addParam(ParamWidget::create(Vec(137, 265), module, RouletteLFO::FREQY2_PARAM, -8.0, 3.0, 1.0)); addInput(Port::create(Vec(13, 219), Port::INPUT, module, RouletteLFO::FIXED_RADIUS_INPUT)); addInput(Port::create(Vec(63, 219), Port::INPUT, module, RouletteLFO::ROATATING_RADIUS_INPUT)); addInput(Port::create(Vec(116, 219), Port::INPUT, module, RouletteLFO::DISTANCE_INPUT)); addInput(Port::create(Vec(163, 219), Port::INPUT, module, RouletteLFO::FREQUENCY_INPUT)); addOutput(Port::create(Vec(57, 335), Port::OUTPUT, module, RouletteLFO::OUTPUT_X)); addOutput(Port::create(Vec(113, 335), Port::OUTPUT, module, RouletteLFO::OUTPUT_Y)); //addChild(ModuleLightWidget::create>(Vec(21, 59), module, LissajousLFO::BLINK_LIGHT_1)); //addChild(ModuleLightWidget::create>(Vec(41, 59), module, LissajousLFO::BLINK_LIGHT_2)); //addChild(ModuleLightWidget::create>(Vec(61, 59), module, LissajousLFO::BLINK_LIGHT_3)); //addChild(ModuleLightWidget::create>(Vec(81, 59), module, LissajousLFO::BLINK_LIGHT_4)); } } // namespace rack_plugin_FrozenWasteland using namespace rack_plugin_FrozenWasteland; RACK_PLUGIN_MODEL_INIT(FrozenWasteland, RouletteLFO) { Model *modelRouletteLFO = Model::create("Frozen Wasteland", "RouletteLFO", "Roulette LFO", LFO_TAG); return modelRouletteLFO; }