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");