#include #include "Southpole.hpp" #include "dsp/digital.hpp" #include "Bjorklund.hpp" namespace rack_plugin_Southpole { struct Sns : Module { enum ParamIds { K_PARAM, L_PARAM, R_PARAM, S_PARAM, P_PARAM, A_PARAM, CLK_PARAM, TRIG_PARAM, NUM_PARAMS }; enum InputIds { K_INPUT, L_INPUT, R_INPUT, S_INPUT, A_INPUT, P_INPUT, CLK_INPUT, RESET_INPUT, NUM_INPUTS }; enum OutputIds { GATE_OUTPUT, ACCENT_OUTPUT, CLK_OUTPUT, RESET_OUTPUT, NUM_OUTPUTS }; enum LightIds { NUM_LIGHTS }; Bjorklund euclid; Bjorklund euclid2; #define MAXLEN 32 // calculated sequence/accents std::vector seq0; std::vector acc0; //padded+rotated+distributed std::array sequence; std::array accents; bool calculate; bool from_reset; enum patternStyle { EUCLIDEAN_PATTERN, RANDOM_PATTERN, FIBONACCI_PATTERN, LINEAR_PATTERN, CANTOR_PATTERN } style = EUCLIDEAN_PATTERN; unsigned int par_k=4; // fill unsigned int par_l=10; // pattern length unsigned int par_r=1; // rotation unsigned int par_p=1; // padding unsigned int par_s=1; // shift unsigned int par_a=3; // accent unsigned int par_last; // checksum unsigned int par_k_last; unsigned int par_l_last; unsigned int par_a_last; SchmittTrigger clockTrigger; SchmittTrigger resetTrigger; PulseGenerator gatePulse; PulseGenerator accentPulse; bool gateOn; bool accOn; enum gateModes { TRIGGER_MODE, GATE_MODE, TURING_MODE } gateMode = TRIGGER_MODE; unsigned int currentStep = 0; unsigned int turing = 0; Sns() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { sequence.fill(0); accents.fill(0); reset(); } void step() override; void reset() override; unsigned int fib(unsigned int n){ return (n < 2) ? n : fib(n - 1) + fib(n - 2); } json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "mode", json_integer((int) gateMode)); json_object_set_new(rootJ, "style", json_integer((int) style)); return rootJ; } void fromJson(json_t *rootJ) override { json_t *modeJ = json_object_get(rootJ, "mode"); if (modeJ) { gateMode = (gateModes) json_integer_value(modeJ); } json_t *styleJ = json_object_get(rootJ, "style"); if (styleJ) { style = (patternStyle) json_integer_value(styleJ); } } }; void Sns::reset() { if ( par_l_last != par_l ) { std::fill(seq0.begin(), seq0.end(), 0); seq0.resize(par_l+par_p); } if ( par_k_last != par_k ) { std::fill(acc0.begin(), acc0.end(), 0); acc0.resize(par_k); } if ( style == RANDOM_PATTERN ) { if ( par_l_last != par_l || par_k_last != par_k) { int n = 0; seq0.resize(par_l); std::fill(seq0.begin(), seq0.end(), 0); unsigned int f = 0; while ( f < par_k ) { if ( randomUniform() < (float)par_k/(float)par_l ) { seq0.at(n % par_l) = 1; f++; } n++; } } if ( par_a && (par_a_last != par_a || par_k_last != par_k) ) { int n = 0; acc0.resize(par_k); std::fill(acc0.begin(), acc0.end(), 0); unsigned int nacc = 0; while ( nacc < par_a ) { if ( randomUniform() < (float)par_a/(float)par_k ) { acc0.at(n % par_k) = 1; nacc++; } n++; } } } if ( style == FIBONACCI_PATTERN ) { seq0.resize(par_l); std::fill(seq0.begin(), seq0.end(), 0); for ( unsigned int k = 0; k < par_k; k++ ) { seq0.at(fib(k) % par_l) = 1; } acc0.resize(par_k); std::fill(acc0.begin(), acc0.end(), 0); for ( unsigned int a = 0; a < par_a; a++ ) { acc0.at(fib(a) % par_k) = 1; } } // if ( style == CANTOR_PATTERN ) { if ( style == LINEAR_PATTERN ) { seq0.resize(par_l); std::fill(seq0.begin(), seq0.end(), 0); for ( unsigned int k = 0; k < par_k; k++ ) { seq0.at( par_l*k/par_k ) = 1; } acc0.resize(par_k); std::fill(acc0.begin(), acc0.end(), 0); for ( unsigned int a = 0; a < par_a; a++ ) { acc0.at( par_k*a/par_a ) = 1; } } if ( style == EUCLIDEAN_PATTERN ) { euclid.reset(); euclid.init(par_l,par_k); euclid.iter(); euclid2.reset(); if (par_a>0) { euclid2.init(par_k, par_a); euclid2.iter(); } seq0 = euclid.sequence; acc0 = euclid2.sequence; } // pad sequence //if (seq0.size() != par_l+par_p) seq0.resize(par_l+par_p); //for (unsigned int i=par_l; i= par_l + par_p) { currentStep = 0; } if ( gateMode == TURING_MODE ) { turing = 0; for (unsigned int i=0; i font; float y1; float yh; SnsDisplay( float y1_, float yh_ ) { y1 = y1_; yh = yh_; //font = Font::load(assetPlugin(plugin, "res/fonts/Sudo.ttf")); font = Font::load(assetPlugin(plugin, "res/hdad-segment14-1.002/Segment14.ttf")); } void drawPolygon(NVGcontext *vg) { Rect b = Rect(Vec(2, 2), box.size.minus(Vec(2, 2))); float cx = 0.5*b.size.x+1; float cy = 0.5*b.size.y-12; const float r1 = .45*b.size.x; const float r2 = .35*b.size.x; // Circles nvgBeginPath(vg); nvgStrokeColor(vg, nvgRGBA(0x7f, 0x00, 0x00, 0xff)); nvgFillColor(vg, nvgRGBA(0xff, 0x00, 0x00, 0xff)); nvgStrokeWidth(vg, 1.); nvgCircle(vg, cx, cy, r1); nvgCircle(vg, cx, cy, r2); nvgStroke(vg); unsigned len = module->par_l + module->par_p; nvgStrokeColor(vg, nvgRGBA(0xff, 0x00, 0x00, 0xff)); nvgBeginPath(vg); bool first = true; // inactive Step Rings for (unsigned i = 0; i < len; i++) { if ( !module->sequence[i] ) { float r = module->accents[i] ? r1 : r2; float x = cx + r * cosf(2.*M_PI*i/len-.5*M_PI); float y = cy + r * sinf(2.*M_PI*i/len-.5*M_PI); nvgBeginPath(vg); nvgFillColor(vg, nvgRGBA(0x30, 0x10, 0x10, 0xff)); nvgStrokeWidth(vg, 1.); nvgStrokeColor(vg, nvgRGBA(0x7f, 0x00, 0x00, 0xff)); nvgCircle(vg, x, y, 3.); nvgFill(vg); nvgStroke(vg); } } // Path nvgBeginPath(vg); nvgStrokeColor(vg, nvgRGBA(0xff, 0x00, 0x00, 0xff)); nvgStrokeWidth(vg, 1.); for (unsigned int i = 0; i < len; i++) { if ( module->sequence[i] ) { float a = i/float(len); float r = module->accents[i] ? r1 : r2; float x = cx + r * cosf(2.*M_PI*a-.5*M_PI); float y = cy + r * sinf(2.*M_PI*a-.5*M_PI); Vec p(x,y); if (module->par_k == 1) nvgCircle(vg, x, y, 3.); if (first) { nvgMoveTo(vg, p.x, p.y); first = false; } else { nvgLineTo(vg, p.x, p.y); } } } nvgClosePath(vg); nvgStroke(vg); // Active Step Rings for (unsigned i = 0; i < len; i++) { if ( module->sequence[i] ) { float r = module->accents[i] ? r1 : r2; float x = cx + r * cosf(2.*M_PI*i/len-.5*M_PI); float y = cy + r * sinf(2.*M_PI*i/len-.5*M_PI); nvgBeginPath(vg); nvgFillColor(vg, nvgRGBA(0x30, 0x10, 0x10, 0xff)); nvgStrokeWidth(vg, 1.); nvgStrokeColor(vg, nvgRGBA(0xff, 0x00, 0x00, 0xff)); nvgCircle(vg, x, y, 3.); nvgFill(vg); nvgStroke(vg); } } unsigned int i = module->currentStep; float r = module->accents[i] ? r1 : r2; float x = cx + r * cosf(2.*M_PI*i/len-.5*M_PI); float y = cy + r * sinf(2.*M_PI*i/len-.5*M_PI); nvgBeginPath(vg); nvgStrokeColor(vg, nvgRGBA(0xff, 0x00, 0x00, 0xff)); if ( module->sequence[i] ) { nvgFillColor(vg, nvgRGBA(0xff, 0x00, 0x00, 0xff)); } else { nvgFillColor(vg, nvgRGBA(0x30, 0x10, 0x10, 0xff)); } nvgCircle(vg, x, y, 3.); nvgStrokeWidth(vg, 1.5); nvgFill(vg); nvgStroke(vg); } void draw(NVGcontext *vg) override { // i know ... shouldn't be here at all if (module->calculate) { module->reset(); module->par_k_last = module->par_k; module->par_l_last = module->par_l; module->par_a_last = module->par_a; } // Background NVGcolor backgroundColor = nvgRGB(0x30, 0x10, 0x10); NVGcolor borderColor = nvgRGB(0xd0, 0xd0, 0xd0); nvgBeginPath(vg); nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 5.0); nvgFillColor(vg, backgroundColor); nvgFill(vg); nvgStrokeWidth(vg, 1.5); nvgStrokeColor(vg, borderColor); nvgStroke(vg); drawPolygon(vg); nvgFontSize(vg, 8); nvgFontFaceId(vg, font->handle); Vec textPos = Vec(15, 105); NVGcolor textColor = nvgRGB(0xff, 0x00, 0x00); nvgFillColor(vg, textColor); char str[20]; snprintf(str,sizeof(str),"%2d %2d %2d",int(module->par_k),int(module->par_l),int(module->par_r)); nvgText(vg, textPos.x, textPos.y-11, str, NULL); snprintf(str,sizeof(str),"%2d %2d %2d",int(module->par_p),int(module->par_a),int(module->par_s)); nvgText(vg, textPos.x, textPos.y, str, NULL); } }; struct SnsWidget : ModuleWidget { SnsWidget(); Menu *createContextMenu() override; SnsWidget(Sns *module) : ModuleWidget(module) { box.size = Vec(15*6, 380); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/Sns.svg"))); addChild(panel); } { SnsDisplay *display = new SnsDisplay(180,30); display->module = module; display->box.pos = Vec( 3., 30); display->box.size = Vec(box.size.x-6., 110. ); addChild(display); } const float y1 = 160; const float yh = 30; float x1 = 4.; float x2 = 4.+30; float x3 = 4.+60; addParam(ParamWidget::create(Vec(x1, y1 ), module, Sns::K_PARAM, 0., 1., .25)); addParam(ParamWidget::create(Vec(x2, y1 ), module, Sns::L_PARAM, 0., 1., 1.)); addParam(ParamWidget::create(Vec(x3, y1 ), module, Sns::R_PARAM, 0., 1., 0.)); addInput(Port::create(Vec(x1, y1+1*yh), Port::INPUT, module, Sns::K_INPUT)); addInput(Port::create(Vec(x2, y1+1*yh), Port::INPUT, module, Sns::L_INPUT)); addInput(Port::create(Vec(x3, y1+1*yh), Port::INPUT, module, Sns::R_INPUT)); addParam(ParamWidget::create(Vec(x1, y1+2.5*yh), module, Sns::P_PARAM, 0., 1., 0.)); addParam(ParamWidget::create(Vec(x2, y1+2.5*yh), module, Sns::A_PARAM, 0., 1., 0.)); addParam(ParamWidget::create(Vec(x3, y1+2.5*yh), module, Sns::S_PARAM, 0., 1., 0.)); addInput(Port::create(Vec(x1, y1+3.5*yh), Port::INPUT, module, Sns::P_INPUT)); addInput(Port::create(Vec(x2, y1+3.5*yh), Port::INPUT, module, Sns::A_INPUT)); addInput(Port::create(Vec(x3, y1+3.5*yh), Port::INPUT, module, Sns::S_INPUT)); addInput(Port::create(Vec(x1, y1+4.65*yh), Port::INPUT, module, Sns::CLK_INPUT)); addInput(Port::create(Vec(x1, y1+5.4*yh), Port::INPUT, module, Sns::RESET_INPUT)); addOutput(Port::create(Vec(x3, y1+4.65*yh), Port::OUTPUT, module, Sns::CLK_OUTPUT)); addOutput(Port::create(Vec(x3, y1+5.4*yh), Port::OUTPUT, module, Sns::RESET_OUTPUT)); addOutput(Port::create(Vec(x2, y1+4.65*yh), Port::OUTPUT, module, Sns::GATE_OUTPUT)); addOutput(Port::create(Vec(x2, y1+5.4*yh), Port::OUTPUT, module, Sns::ACCENT_OUTPUT)); //addChild(ModuleLightWidget::create>(Vec(4, 281), module, Sns::CLK_LIGHT)); //addChild(ModuleLightWidget::create>(Vec(4+25, 281), module, Sns::GATE_LIGHT)); //addChild(ModuleLightWidget::create>(Vec(4+50, 281), module, Sns::ACCENT_LIGHT)); } }; struct SnsGateModeItem : MenuItem { Sns *sns; Sns::gateModes gm; void onAction(EventAction &e) override { sns->gateMode = gm; } void step() override { rightText = (sns->gateMode == gm) ? "✔" : ""; MenuItem::step(); } }; struct SnsPatternStyleItem : MenuItem { Sns *sns; Sns::patternStyle ps; void onAction(EventAction &e) override { sns->style = ps; sns->reset(); } void step() override { rightText = (sns->style == ps) ? "✔" : ""; MenuItem::step(); } }; Menu *SnsWidget::createContextMenu() { Sns *sns = dynamic_cast(module); assert(sns); Menu *menu = ModuleWidget::createContextMenu(); menu->addChild(construct());; menu->addChild(construct(&MenuLabel::text, "Gate Mode")); menu->addChild(construct(&MenuItem::text, "Trigger", &SnsGateModeItem::sns, sns, &SnsGateModeItem::gm, Sns::TRIGGER_MODE)); menu->addChild(construct(&MenuItem::text, "Gate", &SnsGateModeItem::sns, sns, &SnsGateModeItem::gm, Sns::GATE_MODE)); menu->addChild(construct(&MenuItem::text, "Turing", &SnsGateModeItem::sns, sns, &SnsGateModeItem::gm, Sns::TURING_MODE)); menu->addChild(construct());; menu->addChild(construct(&MenuLabel::text, "Pattern Style")); menu->addChild(construct(&MenuItem::text, "Euclid", &SnsPatternStyleItem::sns, sns, &SnsPatternStyleItem::ps, Sns::EUCLIDEAN_PATTERN)); menu->addChild(construct(&MenuItem::text, "Fibonacci", &SnsPatternStyleItem::sns, sns, &SnsPatternStyleItem::ps, Sns::FIBONACCI_PATTERN)); menu->addChild(construct(&MenuItem::text, "Random", &SnsPatternStyleItem::sns, sns, &SnsPatternStyleItem::ps, Sns::RANDOM_PATTERN)); //menu->addChild(construct(&MenuItem::text, "Cantor", &SnsPatternStyleItem::sns, sns, &SnsPatternStyleItem::ps, Sns::CANTOR_PATTERN)); menu->addChild(construct(&MenuItem::text, "Linear", &SnsPatternStyleItem::sns, sns, &SnsPatternStyleItem::ps, Sns::LINEAR_PATTERN)); return menu; } } // namespace rack_plugin_Southpole using namespace rack_plugin_Southpole; RACK_PLUGIN_MODEL_INIT(Southpole, Sns) { Model *modelSns = Model::create( "Southpole", "SNS", "SNS - euclidean sequencer", SEQUENCER_TAG); return modelSns; }