computerscare-vcv-modules

ComputerScare modules for VCV Rack
Log | Files | Refs

ComputerscareFolyPace.cpp (13363B)


      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 FolyPace : Module {
     10 	enum ParamIds {
     11 		TIME_PARAM,
     12 		TRIM,
     13 		OFFSET,
     14 		SCRAMBLE,
     15 		NUM_PARAMS
     16 	};
     17 	enum InputIds {
     18 		X_INPUT,
     19 		NUM_INPUTS
     20 	};
     21 	enum OutputIds {
     22 		NUM_OUTPUTS
     23 	};
     24 	enum LightIds {
     25 		NUM_LIGHTS
     26 	};
     27 
     28 	float bufferX[16][BUFFER_SIZE] = {};
     29 	int channelsX = 0;
     30 	int bufferIndex = 0;
     31 	int frameIndex = 0;
     32 	float lastScramble = 0;
     33 	int cnt = 0;
     34 	int cmap[16];
     35 
     36 	int A = 31;
     37 	int B = 32;
     38 	int C = 29;
     39 	int D = 2;
     40 
     41 	bool faceEmitsLight = true;
     42 
     43 	FolyPace() {
     44 		config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
     45 		const float timeBase = (float) BUFFER_SIZE / 6;
     46 		for (int i = 0; i < 16; i++) {
     47 			cmap[i] = i;
     48 		}
     49 		configParam(TIME_PARAM, 6.f, 16.f, 14.f, "Time", " ms/div", 1 / 2.f, 1000 * timeBase);
     50 
     51 		configParam(TRIM, -2.f, 2.f, 0.2f, "Input Trim");
     52 		configParam(OFFSET, -5.f, 5.f, 0.f, "Input Offset", " Volts");
     53 		configParam(SCRAMBLE, -10.f, 10.f, 0.f, "Scrambling");
     54 
     55 		configInput(X_INPUT, "Main");
     56 
     57 
     58 	}
     59 
     60 	void onReset() override {
     61 		//std::memset(bufferX, 0, sizeof(bufferX));
     62 	}
     63 	void updateScramble(float v) {
     64 		for (int i = 0; i < 16; i++) {
     65 			cmap[i] = (i * A + B + (int)std::floor(v * 1010.1)) % 16;
     66 		}
     67 	}
     68 	void checkScramble() {
     69 		float xx = params[SCRAMBLE].getValue();
     70 		if (lastScramble != xx) {
     71 			lastScramble = xx;
     72 			updateScramble(xx);
     73 		}
     74 	}
     75 
     76 	void process(const ProcessArgs &args) override {
     77 		// Modes
     78 		// Compute time
     79 		float deltaTime = std::pow(2.f, -params[TIME_PARAM].getValue());
     80 
     81 		int frameCount = (int) std::ceil(deltaTime * args.sampleRate);
     82 
     83 		// Set channels
     84 		int channelsX = inputs[X_INPUT].getChannels();
     85 		if (channelsX != this->channelsX) {
     86 			std::memset(bufferX, 0, sizeof(bufferX));
     87 			this->channelsX = channelsX;
     88 		}
     89 		if (cnt > 4101) {
     90 
     91 			checkScramble();
     92 			cnt = 0;
     93 		}
     94 		cnt++;
     95 		// Add frame to buffer
     96 		if (bufferIndex < BUFFER_SIZE) {
     97 			if (++frameIndex > frameCount) {
     98 				frameIndex = 0;
     99 				float trimVal = params[TRIM].getValue();
    100 				float offsetVal = params[OFFSET].getValue();
    101 				/*
    102 
    103 				if (inputs[X_INPUT].isConnected()) {
    104 									for (int c = 0; c < 16; c++) {
    105 										bufferX[c][bufferIndex] = inputs[X_INPUT].getVoltage(std::min(cmap[c], this->channelsX)) * trimVal + offsetVal + 99 + (1071 * cmap[c]) % 19;
    106 										//bufferX[c][bufferIndex]=inputs[X_INPUT].getVoltage(cmap[c])
    107 									}
    108 								}
    109 								else {
    110 									for (int c = 0; c < 16; c++) {
    111 										bufferX[c][bufferIndex] = offsetVal + 99 + (1071 * cmap[c]) % 19;
    112 									}
    113 								}
    114 				*/
    115 				if (inputs[X_INPUT].isConnected()) {
    116 					for (int c = 0; c < 16; c++) {
    117 						bufferX[c][bufferIndex] = inputs[X_INPUT].getVoltage(std::min(cmap[c], this->channelsX)) * trimVal + offsetVal + 99 + (1071 * cmap[c]) % 19;
    118 					}
    119 				}
    120 				else {
    121 					for (int c = 0; c < 16; c++) {
    122 						bufferX[c][bufferIndex] = offsetVal + 99 + (1071 * cmap[c]) % 19;
    123 					}
    124 				}
    125 
    126 				bufferIndex++;
    127 			}
    128 		}
    129 
    130 		// Don't wait for trigger if still filling buffer
    131 		if (bufferIndex < BUFFER_SIZE) {
    132 			return;
    133 		}
    134 
    135 		// Trigger immediately if external but nothing plugged in, or in Lissajous mode
    136 		if (true) {
    137 			trigger();
    138 			return;
    139 		}
    140 
    141 		frameIndex++;
    142 
    143 
    144 	}
    145 
    146 	void trigger() {
    147 		bufferIndex = 0;
    148 		frameIndex = 0;
    149 	}
    150 	json_t* dataToJson() override {
    151 		json_t* rootJ = json_object();
    152 
    153 		json_object_set_new(rootJ, "faceEmitsLight", json_boolean(faceEmitsLight));
    154 
    155 		return rootJ;
    156 	}
    157 
    158 	void dataFromJson(json_t* rootJ) override {
    159 		json_t* faceEmitsLightJ = json_object_get(rootJ, "faceEmitsLight");
    160 		if (faceEmitsLightJ)
    161 			faceEmitsLight = json_boolean_value(faceEmitsLightJ);
    162 	}
    163 };
    164 
    165 
    166 struct FolyPaceDisplay : TransparentWidget {
    167 	FolyPace *module;
    168 
    169 	FolyPaceDisplay() {
    170 	}
    171 
    172 	void drawFace(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) {
    173 
    174 
    175 
    176 		float sf = 1 + 0.2 * sin(B - C); //scaleFactor
    177 		float ox = 67.5 + sf * 20.33 * sin(D - C / 2);
    178 		float oy = 180 + sf * 30.33 * sin(G - F);
    179 
    180 		float h = 0.4 + 0.3 * sin(A / 2) + 0.3 * sin(K / 3);	//face hue
    181 		float s = 0.5 + 0.32 * sin(B / 3 - 33.21 - D / 2);	//face saturation
    182 		float l = 0.5 + 0.35 * sin(C / 2);	//face lightness
    183 		float fx = ox ;	//face y
    184 		float fy = oy;	//face x
    185 
    186 
    187 		float frx = sf * (70 + 40 * sin(A - B / 2));	// face x radius
    188 		float fry = sf * (150 + 80 * sin(F / 2.2)); //face y radius
    189 		float fr = 0.04 * sin(H - M) + 0.02 * sin(H / 3 + 2.2) + 0.02 * sin(L + P + 8.222); //face rotation
    190 		NVGcolor faceColor = nvgHSLA(h, s, l, 0xff);
    191 
    192 		float epx = ox;
    193 		float epy = oy - 10 * (2 + sf + sin(I - J / 2));
    194 
    195 		float eyeSpacing = frx / 2 * (1.8 + 0.5 * sin(200 - J));
    196 		float erlx = frx / 4 * (1 + 0.4 * sin(G));
    197 		float erly = frx / 4 * (1 + 0.4 * sin(H - N + 100));
    198 
    199 		float irisRad = erly * 0.4 * (1.3 + 0.4 * sin(K - D + 1));
    200 		float pupilRad = irisRad * 0.4 * (1 + 0.6 * sin(E));
    201 
    202 		float gazeDir = 3.14159 * (1 + sin(B - K));
    203 		float gazeStrength = 4 * (1.3 + 0.5 * sin(D - 1) + 0.6 * sin(1 - L / 2));
    204 
    205 		NVGcolor irisColor = nvgHSLA(l, s, h, 0xff);
    206 		NVGcolor pupilColor = nvgHSLA(0.1, 0.1, 0.1, 0xff);
    207 
    208 		//nvgSave(args.vg);
    209 
    210 		Rect b = Rect(Vec(0, 0), box.size);
    211 
    212 		nvgFillColor(args.vg, faceColor);
    213 		nvgScissor(args.vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
    214 
    215 		nvgRotate(args.vg, fr);
    216 
    217 		drawHead(args, fx, fy, frx, fry, faceColor);
    218 
    219 
    220 		float leftEyebrowHeight = erly * (1.9 + 0.6 * sin(G) + 0.3 * sin(K - B / 2));
    221 		float rightEyebrowHeight = erly * (1.9 + 0.6 * sin(G - 2.2 + N) + 0.2 * sin(L + 33));
    222 		float leftEyebrowAngle = 0.5 * sin(C) + 0.2 * sin(H / 2 - 2);
    223 		float rightEyebrowAngle = 0.7 * sin(F) + 0.3 * sin(2 - I);
    224 		NVGcolor eyebrowColor = nvgHSLA(0.1, 0.2, 0.2, 0xff);
    225 		float eyebrowThickness = 5.f * (1.3 + sin(M - 2));
    226 		float eyebrowLength = frx * 0.3 * (2.2 + sin(G) + 0.4 * sin(B - 2));
    227 
    228 		drawEyes(args, epx, epy, eyeSpacing, erlx, erly, 1, irisRad, pupilRad, gazeDir, gazeStrength, irisColor, pupilColor);
    229 		drawEyebrows(args, epx, epy, eyeSpacing, leftEyebrowHeight, rightEyebrowHeight, leftEyebrowAngle, rightEyebrowAngle, eyebrowColor, eyebrowThickness, eyebrowLength);
    230 
    231 
    232 		float mouthX = ox;
    233 		float mouthY = oy + 0.4 * fry * (1 + 0.4 * sin(C / 2));
    234 		float mouthWidth = frx * 0.6 * (1.2 + 0.6 * sin(C));
    235 		float mouthOpen = fry * 0.06 * (1 + sin(O) - sin(A * 2 + 44));
    236 		float mouthSmile = sin(D) * 2.0;
    237 		float mouthSkew = sin(L) - sin(H);
    238 		float mouthThickness = 5.4 * (sin(H) - sin(M / 2));
    239 		NVGcolor mouthLipColor = nvgHSLA(0.1 * sin(N) - 0.1, 0.6 + 0.3 * sin(M), 0.5 + .4 * sin(I), 0xff);
    240 
    241 		nvgGlobalCompositeOperation(args.vg, NVG_ATOP);
    242 
    243 
    244 		drawMouth(args, mouthX, mouthY, mouthWidth, mouthOpen, mouthSmile, mouthSkew, mouthThickness, mouthLipColor);
    245 
    246 
    247 
    248 		nvgResetScissor(args.vg);
    249 		//nvgRestore(args.vg);
    250 	}
    251 
    252 	void drawEyebrows(const DrawArgs &args, float x, float y, float eyeSpacing, float leftEyebrowHeight, float rightEyebrowHeight, float leftEyebrowAngle, float rightEyebrowAngle, NVGcolor eyebrowColor, float eyebrowThickness, float eyebrowLength) {
    253 		nvgBeginPath(args.vg);
    254 		nvgStrokeColor(args.vg, eyebrowColor);
    255 		nvgStrokeWidth(args.vg, eyebrowThickness);
    256 		float cosLeft = cos(leftEyebrowAngle);
    257 		float sinLeft = sin(leftEyebrowAngle);
    258 		float cosRight = cos(rightEyebrowAngle);
    259 		float sinRight = sin(rightEyebrowAngle);
    260 
    261 		float r = eyebrowLength / 2;
    262 
    263 		nvgMoveTo(args.vg, x - eyeSpacing / 2 - r * cosLeft, y - leftEyebrowHeight - r * sinLeft);
    264 		nvgLineTo(args.vg, x - eyeSpacing / 2 + r * cosLeft, y - leftEyebrowHeight + r * sinLeft);
    265 		//nvgStroke(args.vg);
    266 
    267 		nvgMoveTo(args.vg, x + eyeSpacing / 2 - r * cosRight, y - rightEyebrowHeight - r * sinRight);
    268 		nvgLineTo(args.vg, x + eyeSpacing / 2 + r * cosRight, y - rightEyebrowHeight + r * sinRight);
    269 		nvgStroke(args.vg);
    270 
    271 		nvgClosePath(args.vg);
    272 	}
    273 	void drawHead(const DrawArgs &args, float x, float y, float width, float height, NVGcolor color) {
    274 		nvgBeginPath(args.vg);
    275 
    276 
    277 		nvgLineCap(args.vg, NVG_ROUND);
    278 		nvgMiterLimit(args.vg, 2.f);
    279 		nvgStrokeWidth(args.vg, 4.5f);
    280 
    281 		nvgEllipse(args.vg, x, y, width, height);
    282 		nvgClosePath(args.vg);
    283 		nvgGlobalCompositeOperation(args.vg, NVG_SOURCE_OVER);
    284 		nvgFill(args.vg);
    285 	}
    286 	void drawMouth(const DrawArgs &args, float x, float y, float width, float open, float smile, float skew, float thickness, NVGcolor lipColor) {
    287 		nvgBeginPath(args.vg);
    288 		nvgStrokeWidth(args.vg, thickness);
    289 		nvgStrokeColor(args.vg, lipColor);
    290 		//nvgStrokeWidth(args.vg, 4.5f);
    291 
    292 		nvgMoveTo(args.vg, x - width / 2, y - 20.f * smile);
    293 
    294 		//top
    295 		nvgBezierTo(args.vg, x - width / 4, y - open * smile, x + width / 4, y - open * smile, x + width / 2, y - 10.f * smile);
    296 
    297 		//bottom
    298 		nvgBezierTo(args.vg, x + width / 4, y + smile * open, x - width / 4, y + smile * open, x - width / 2, y - 20.f * smile);
    299 		nvgClosePath(args.vg);
    300 		nvgGlobalCompositeOperation(args.vg, NVG_ATOP);
    301 		nvgFillColor(args.vg, nvgRGBA(0, 0, 0, 0xff));
    302 		nvgStroke(args.vg);
    303 		nvgFill(args.vg);
    304 
    305 	}
    306 	void drawEyes(const DrawArgs &args, float x, float y, float spacing, float rx, float ry, float open, float irisRad, float pupilRad, float gazeDir, float gazeStrength, NVGcolor irisColor, NVGcolor pupilColor) {
    307 		float leftX = x - spacing / 2;
    308 		float rightX = x + spacing / 2;
    309 		float pupilOffsetX = gazeStrength * cos(gazeDir);
    310 		float pupilOffsetY = gazeStrength * sin(gazeDir);
    311 		float leftPupilX = leftX + pupilOffsetX;
    312 		float leftPupilY = y + pupilOffsetY;
    313 		float rightPupilX = rightX + pupilOffsetX;
    314 		float rightPupilY = y + pupilOffsetY;
    315 
    316 		nvgBeginPath(args.vg);
    317 		nvgStrokeWidth(args.vg, 1.f);
    318 		//nvgStrokeWidth(args.vg, 4.5f);
    319 
    320 		//whites of eyes
    321 		nvgFillColor(args.vg, nvgRGB(250, 250, 250));
    322 
    323 
    324 		nvgEllipse(args.vg, leftX, y, rx, ry);
    325 		nvgEllipse(args.vg, rightX, y, rx, ry);
    326 
    327 		nvgGlobalCompositeOperation(args.vg, NVG_SOURCE_OVER);
    328 		nvgFill(args.vg);
    329 		nvgClosePath(args.vg);
    330 
    331 		//iris
    332 
    333 		nvgBeginPath(args.vg);
    334 		nvgFillColor(args.vg, irisColor);
    335 
    336 
    337 		nvgCircle(args.vg, leftPupilX, leftPupilY, irisRad);
    338 		nvgCircle(args.vg, rightPupilX, rightPupilY, irisRad);
    339 		nvgGlobalCompositeOperation(args.vg, NVG_ATOP);
    340 		nvgFill(args.vg);
    341 		nvgClosePath(args.vg);
    342 
    343 		//pupil
    344 		nvgBeginPath(args.vg);
    345 		nvgFillColor(args.vg, pupilColor);
    346 
    347 
    348 		nvgCircle(args.vg, leftPupilX, leftPupilY, pupilRad);
    349 		nvgCircle(args.vg, rightPupilX, rightPupilY, pupilRad);
    350 		nvgGlobalCompositeOperation(args.vg, NVG_ATOP);
    351 		nvgFill(args.vg);
    352 		nvgClosePath(args.vg);
    353 
    354 		nvgGlobalCompositeOperation(args.vg, NVG_SOURCE_OVER);
    355 //eyebrows
    356 
    357 	}
    358 	void drawNose(const DrawArgs &args, float x, float y, float height, float width, float thickness, NVGcolor color) {
    359 
    360 	}
    361 
    362 
    363 	void draw(const DrawArgs &args) override {
    364 		if (!module) {
    365 			drawFace(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);
    366 		}
    367 		else {
    368 			if (!module->faceEmitsLight) {
    369 				drawFace(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]);
    370 			}
    371 		}
    372 	}
    373 	void drawLayer(const BGPanel::DrawArgs& args, int layer) override {
    374 		if (layer == 1 && module) {
    375 			if (module->faceEmitsLight) {
    376 				drawFace(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]);
    377 			}
    378 		}
    379 		Widget::drawLayer(args, layer);
    380 	}
    381 };
    382 
    383 
    384 struct FolyPaceWidget : ModuleWidget {
    385 	FolyPaceWidget(FolyPace *module) {
    386 		setModule(module);
    387 
    388 		box.size = Vec(9 * 15, 380);
    389 		{
    390 			ComputerscareSVGPanel *panel = new ComputerscareSVGPanel();
    391 			panel->box.size = box.size;
    392 			panel->setBackground(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ComputerscareFolyPacePanel.svg")));
    393 			addChild(panel);
    394 
    395 		}
    396 
    397 		{
    398 			FolyPaceDisplay *display = new FolyPaceDisplay();
    399 			display->module = module;
    400 			display->box.pos = Vec(0, 0);
    401 			display->box.size = Vec(box.size.x, box.size.y);
    402 			addChild(display);
    403 		}
    404 
    405 		addInput(createInput<PointingUpPentagonPort>(Vec(1, 353), module, FolyPace::X_INPUT));
    406 		addParam(createParam<SmallKnob>(Vec(31, 357), module, FolyPace::TRIM));
    407 		addParam(createParam<SmoothKnob>(Vec(51, 353), module, FolyPace::OFFSET));
    408 		addParam(createParam<ScrambleKnob>(Vec(81, 357), module, FolyPace::SCRAMBLE));
    409 	}
    410 
    411 	void appendContextMenu(Menu* menu) override {
    412 		FolyPace* module = dynamic_cast<FolyPace*>(this->module);
    413 
    414 		menu->addChild(new MenuSeparator);
    415 
    416 		menu->addChild(createBoolPtrMenuItem("Face Emits Light", "", &module->faceEmitsLight));
    417 	}
    418 };
    419 
    420 
    421 Model *modelComputerscareFolyPace = createModel<FolyPace, FolyPaceWidget>("computerscare-foly-pace");