computerscare-vcv-modules

ComputerScare modules for VCV Rack
Log | Files | Refs

ComputerscareStolyFickPigure.cpp (10928B)


      1 #include <string.h>
      2 #include "Computerscare.hpp"
      3 #include "dtpulse.hpp"
      4 
      5 
      6 static const int BUFFER_SIZE = 512;
      7 
      8 
      9 struct StolyFickPigure : Module {
     10 	enum ParamIds {
     11 		TIME_PARAM,
     12 		TRIM,
     13 		OFFSET,
     14 		SCRAMBLE,
     15 		NUM_PARAMS
     16 	};
     17 	enum InputIds {
     18 		X_INPUT,
     19 		SCRAMBLE_INPUT,
     20 		NUM_INPUTS
     21 	};
     22 	enum OutputIds {
     23 		NUM_OUTPUTS
     24 	};
     25 	enum LightIds {
     26 		NUM_LIGHTS
     27 	};
     28 
     29 	float bufferX[16][BUFFER_SIZE] = {};
     30 	int cmap[16];
     31 	int channelsX = 0;
     32 	int bufferIndex = 0;
     33 	int frameIndex = 0;
     34 	int cnt = 0;
     35 	float lastScramble = 0;
     36 
     37 	int A = 31;
     38 	int B = 32;
     39 	int C = 29;
     40 	int D = 2;
     41 
     42 	bool figureEmitsLight = true;
     43 
     44 
     45 	StolyFickPigure() {
     46 		config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
     47 		const float timeBase = (float) BUFFER_SIZE / 6;
     48 
     49 		for (int i = 0; i < 16; i++) {
     50 			cmap[i] = i;
     51 		}
     52 
     53 		configParam(TIME_PARAM, 6.f, 16.f, 14.f, "Time", " ms/div", 1 / 2.f, 1000 * timeBase);
     54 
     55 		configParam(TRIM, -2.f, 2.f, 0.2f, "Input Trim");
     56 		configParam(OFFSET, -5.f, 5.f, 0.f, "Input Offset", " Volts");
     57 
     58 		configParam(SCRAMBLE, -10.f, 10.f, 0.f, "Scrambling");
     59 
     60 		configInput(X_INPUT, "Main");
     61 
     62 	}
     63 
     64 	void onReset() override {
     65 		//std::memset(bufferX, 0, sizeof(bufferX));
     66 	}
     67 	void updateScramble(float v) {
     68 		for (int i = 0; i < 16; i++) {
     69 			cmap[i] = (i * A + B + (int)std::floor(v * 1010.1)) % 16;
     70 		}
     71 	}
     72 	void checkScramble() {
     73 		float xx = params[SCRAMBLE].getValue();
     74 		if (lastScramble != xx) {
     75 			lastScramble = xx;
     76 			updateScramble(xx);
     77 		}
     78 	}
     79 	void process(const ProcessArgs &args) override {
     80 		// Modes
     81 		// Compute time
     82 		float deltaTime = std::pow(2.f, -params[TIME_PARAM].getValue());
     83 
     84 		int frameCount = (int) std::ceil(deltaTime * args.sampleRate);
     85 
     86 		// Set channels
     87 		int channelsX = inputs[X_INPUT].getChannels();
     88 		if (channelsX != this->channelsX) {
     89 			std::memset(bufferX, 0, sizeof(bufferX));
     90 			this->channelsX = channelsX;
     91 		}
     92 
     93 		if (cnt > 4101) {
     94 
     95 			checkScramble();
     96 			cnt = 0;
     97 		}
     98 		cnt++;
     99 		// Add frame to buffer
    100 		if (bufferIndex < BUFFER_SIZE) {
    101 			if (++frameIndex > frameCount) {
    102 				frameIndex = 0;
    103 				float trimVal = params[TRIM].getValue();
    104 				float offsetVal = params[OFFSET].getValue();
    105 
    106 				if (inputs[X_INPUT].isConnected()) {
    107 					for (int c = 0; c < 16; c++) {
    108 						bufferX[c][bufferIndex] = inputs[X_INPUT].getVoltage(std::min(cmap[c], this->channelsX)) * trimVal + offsetVal + 99 + (1071 * cmap[c]) % 19;
    109 						//bufferX[c][bufferIndex]=inputs[X_INPUT].getVoltage(cmap[c])
    110 					}
    111 				}
    112 				else {
    113 					for (int c = 0; c < 16; c++) {
    114 						bufferX[c][bufferIndex] = offsetVal + 99 + (1071 * cmap[c]) % 19;
    115 					}
    116 				}
    117 
    118 				bufferIndex++;
    119 			}
    120 		}
    121 
    122 		// Don't wait for trigger if still filling buffer
    123 		if (bufferIndex < BUFFER_SIZE) {
    124 			return;
    125 		}
    126 
    127 		// Trigger immediately if external but nothing plugged in, or in Lissajous mode
    128 		if (true) {
    129 			trigger();
    130 			return;
    131 		}
    132 
    133 		frameIndex++;
    134 
    135 
    136 	}
    137 
    138 	void trigger() {
    139 		bufferIndex = 0;
    140 		frameIndex = 0;
    141 	}
    142 
    143 	json_t* dataToJson() override {
    144 		json_t* rootJ = json_object();
    145 
    146 		json_object_set_new(rootJ, "figureEmitsLight", json_boolean(figureEmitsLight));
    147 
    148 		return rootJ;
    149 	}
    150 
    151 	void dataFromJson(json_t* rootJ) override {
    152 		json_t* figureEmitsLightJ = json_object_get(rootJ, "figureEmitsLight");
    153 		if (figureEmitsLightJ)
    154 			figureEmitsLight = json_boolean_value(figureEmitsLightJ);
    155 	}
    156 };
    157 
    158 
    159 struct StolyFickPigureDisplay : TransparentWidget {
    160 	StolyFickPigure *module;
    161 
    162 	StolyFickPigureDisplay() {
    163 	}
    164 
    165 	void drawStickFigure(const DrawArgs &args, float A, float B, float C, float D, float E, float F, float G, float H, float I, float J, float K, float L, float M, float N, float O, float P) {
    166 
    167 		nvgStrokeColor(args.vg, COLOR_COMPUTERSCARE_GREEN);
    168 
    169 		nvgLineJoin(args.vg, NVG_ROUND);
    170 
    171 		float h = 0.5 + 0.25 * sin(C / 2) + 0.25 * sin(K / 3);	//face hue
    172 		float s = 0.5 + 0.32 * sin(B / 3 - 33.21 - D / 2);	//face saturation
    173 		float l = 0.5 + 0.35 * sin(E / 2);	//face lightness
    174 
    175 		NVGcolor faceColor = nvgHSLA(h, s, l, 0xff);
    176 
    177 		nvgFillColor(args.vg, faceColor);
    178 		nvgStrokeWidth(args.vg, 3.2);
    179 
    180 		float size = 1 + sin(O - 29) / 4;
    181 
    182 		//crotch
    183 		float cx = 62 * (1 + (sin(E + F) - sin(P + O / 2 + 50)) / 40000);
    184 		float cy = 210 * (1 + (sin(A + G - 12) - sin(P + H / 2)) / 11000);
    185 
    186 		//thigh spread, length, direction
    187 		float thighSpread = (2 + sin(J + I + K) - sin(A - N / 2)) / 4;
    188 		float thighLength = 50 * (1 + (sin(C - 100 + F + K * 2) + sin(C + L - 10)) / 6);
    189 		float thighDirection = (sin(J + O - 211) - sin(P * 2 + I) - sin(B + K)) / 2;
    190 
    191 
    192 		//ankle spread,length,direction
    193 		float ankleSpread = (2 + sin(O - B) / 2 + sin(F + 2) / 2 + sin(P - E - D + 19.2)) / 13;
    194 		float ankleLength = thighLength * (1 + (sin(F + A + J - K / 2 + 9)) / 9);
    195 		float ankleDirection =  3 * M_PI / 2 + (3 + sin(J + M - L - 101) - sin(P - B + 22) - sin(H)) / 8;
    196 
    197 		float leftKneeArg = 3 * M_PI / 2 + thighDirection + thighSpread;
    198 		float rightKneeArg = 3 * M_PI / 2 + thighDirection - thighSpread;
    199 
    200 
    201 		float leftAnkleArg =  ankleDirection + ankleSpread;
    202 		float rightAnkleArg =  ankleDirection - ankleSpread;
    203 
    204 
    205 		float leftKneeX = cx + thighLength * cos(leftKneeArg);
    206 		float leftKneeY = cy - thighLength * sin(leftKneeArg);
    207 
    208 		float leftAnkleX = leftKneeX + ankleLength * cos(leftAnkleArg);
    209 		float leftAnkleY = leftKneeY - ankleLength * sin(leftAnkleArg);
    210 
    211 		float rightKneeX = cx + thighLength * cos(rightKneeArg);
    212 		float rightKneeY = cy - thighLength * sin(rightKneeArg);
    213 
    214 		float rightAnkleX = rightKneeX + ankleLength * cos(rightAnkleArg);
    215 		float rightAnkleY = rightKneeY - ankleLength * sin(rightAnkleArg);
    216 
    217 
    218 		nvgBeginPath(args.vg);
    219 
    220 
    221 		nvgMoveTo(args.vg, leftAnkleX, leftAnkleY);
    222 		nvgLineTo(args.vg, leftKneeX, leftKneeY);
    223 		nvgLineTo(args.vg, cx, cy);
    224 		nvgLineTo(args.vg, rightKneeX, rightKneeY);
    225 		nvgLineTo(args.vg, rightAnkleX, rightAnkleY);
    226 
    227 		//nvgClosePath(args.vg);
    228 		nvgStroke(args.vg);
    229 
    230 
    231 		//torso length,direction
    232 		float torsoLength = thighLength * (1.4 + (sin(A - 12)) / 4);
    233 		float torsoDirection = M_PI / 2 + sin(D) / 2;
    234 
    235 		float neckX = cx + torsoLength * cos(torsoDirection);
    236 		float neckY = cy - torsoLength * sin(torsoDirection);
    237 
    238 		nvgBeginPath(args.vg);
    239 		nvgMoveTo(args.vg, cx, cy);
    240 		nvgLineTo(args.vg, neckX, neckY);
    241 		nvgStroke(args.vg);
    242 
    243 		float armLength = torsoLength * (2 + (sin(N + 14) - sin(P - L - 3)) / 2) / 4;
    244 		float forearmLength = armLength * (1 + (2 + (sin(F + B + 2) - sin(E))) / 300);
    245 		float armDirection = 3 * M_PI / 2 + 0.2 * (sin(C - M));
    246 		float armSpread = sin(B + P - A) + sin(N - J);
    247 
    248 		float leftElbowArg = armDirection + armSpread;
    249 		float rightElbowArg = armDirection - armSpread;
    250 
    251 		float leftHandArg = sin(E + 22 + A - 4);
    252 		float rightHandArg = sin(F + 22 - B);
    253 
    254 		float leftElbowX = neckX + armLength * cos(leftElbowArg);
    255 		float leftElbowY = neckY - armLength * sin(leftElbowArg);
    256 
    257 		float leftHandX = leftElbowX + forearmLength * cos(leftHandArg);
    258 		float leftHandY = leftElbowY - forearmLength * sin(leftHandArg);
    259 
    260 		float rightElbowX = neckX + armLength * cos(rightElbowArg);
    261 		float rightElbowY = neckY - armLength * sin(rightElbowArg);
    262 
    263 		float rightHandX = rightElbowX + forearmLength * cos(rightHandArg);
    264 		float rightHandY = rightElbowY - forearmLength * sin(rightHandArg);
    265 
    266 		nvgBeginPath(args.vg);
    267 		nvgMoveTo(args.vg, neckX, neckY);
    268 		nvgLineTo(args.vg, leftElbowX, leftElbowY);
    269 		nvgLineTo(args.vg, leftHandX, leftHandY);
    270 		nvgStroke(args.vg);
    271 
    272 		nvgBeginPath(args.vg);
    273 		nvgMoveTo(args.vg, neckX, neckY);
    274 		nvgLineTo(args.vg, rightElbowX, rightElbowY);
    275 		nvgLineTo(args.vg, rightHandX, rightHandY);
    276 		nvgStroke(args.vg);
    277 
    278 		float headHeight = torsoLength * (0.5 + sin(H - E - I - D) / 9 - sin(F + B - C + E) / 7);
    279 		float headWidth = headHeight * (0.6 + sin(I + D - M / 2) / 7 + sin(G / 2 + J - 10) / 6);
    280 		float headAngle = M_PI / 2 + (sin(C + A) / 6 + sin(D + G) / 9);
    281 
    282 		float headRotation = sin(C + A) / 2 + sin(M / 2) / 3;
    283 
    284 		nvgBeginPath(args.vg);
    285 
    286 		nvgTranslate(args.vg, neckX, neckY);
    287 		nvgRotate(args.vg, headRotation);
    288 		nvgEllipse(args.vg, 0, -headHeight, headWidth, headHeight);
    289 
    290 		nvgFill(args.vg);
    291 		nvgStroke(args.vg);
    292 
    293 
    294 		nvgResetScissor(args.vg);
    295 		//nvgRestore(args.vg);
    296 	}
    297 
    298 	void draw(const DrawArgs &args) override {
    299 		if (!module) {
    300 			drawStickFigure(args, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10, random::uniform() * 10);
    301 		}
    302 		else {
    303 			if (!module->figureEmitsLight) {
    304 				drawStickFigure(args, module->bufferX[0][0], module->bufferX[1][0], module->bufferX[2][0], module->bufferX[3][0], module->bufferX[4][0], module->bufferX[5][0], module->bufferX[6][0], module->bufferX[7][0], module->bufferX[8][0], module->bufferX[9][0], module->bufferX[10][0], module->bufferX[11][0], module->bufferX[12][0], module->bufferX[13][0], module->bufferX[14][0], module->bufferX[15][0]);
    305 			}
    306 		}
    307 	}
    308 	void drawLayer(const BGPanel::DrawArgs& args, int layer) override {
    309 		if (layer == 1 && module) {
    310 			if (module->figureEmitsLight) {
    311 				drawStickFigure(args, module->bufferX[0][0], module->bufferX[1][0], module->bufferX[2][0], module->bufferX[3][0], module->bufferX[4][0], module->bufferX[5][0], module->bufferX[6][0], module->bufferX[7][0], module->bufferX[8][0], module->bufferX[9][0], module->bufferX[10][0], module->bufferX[11][0], module->bufferX[12][0], module->bufferX[13][0], module->bufferX[14][0], module->bufferX[15][0]);
    312 			}
    313 		}
    314 		Widget::drawLayer(args, layer);
    315 	}
    316 };
    317 
    318 
    319 struct StolyFickPigureWidget : ModuleWidget {
    320 	StolyFickPigureWidget(StolyFickPigure *module) {
    321 		setModule(module);
    322 
    323 		box.size = Vec(9 * 15, 380);
    324 		{
    325 			ComputerscareSVGPanel *panel = new ComputerscareSVGPanel();
    326 			panel->box.size = box.size;
    327 			panel->setBackground(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ComputerscareStolyFickPigurePanel.svg")));
    328 			addChild(panel);
    329 
    330 		}
    331 
    332 		{
    333 			StolyFickPigureDisplay *display = new StolyFickPigureDisplay();
    334 			display->module = module;
    335 			display->box.pos = Vec(0, 0);
    336 			display->box.size = Vec(box.size.x, box.size.y);
    337 			addChild(display);
    338 		}
    339 
    340 		addInput(createInput<PointingUpPentagonPort>(Vec(1, 353), module, StolyFickPigure::X_INPUT));
    341 		addParam(createParam<SmallKnob>(Vec(31, 357), module, StolyFickPigure::TRIM));
    342 		addParam(createParam<SmoothKnob>(Vec(51, 353), module, StolyFickPigure::OFFSET));
    343 
    344 		addParam(createParam<ScrambleKnob>(Vec(81, 357), module, StolyFickPigure::SCRAMBLE));
    345 	}
    346 
    347 	void appendContextMenu(Menu* menu) override {
    348 		StolyFickPigure* module = dynamic_cast<StolyFickPigure*>(this->module);
    349 
    350 		menu->addChild(new MenuSeparator);
    351 
    352 		menu->addChild(createBoolPtrMenuItem("Stick Figure Emits Light", "", &module->figureEmitsLight));
    353 	}
    354 };
    355 
    356 
    357 Model *modelComputerscareStolyFickPigure = createModel<StolyFickPigure, StolyFickPigureWidget>("computerscare-stoly-fick-pigure");