computerscare-vcv-modules

computerscare modules for VCV Rack
Log | Files | Refs

commit a3153aa0e6920c4197fe84510405d49a4d31bb8a
parent 5ff69894cbe8d5d43987613b5a2dd4b7c03fe372
Author: Adam M <[email protected]>
Date:   Tue, 29 Dec 2020 22:43:26 -0600

Add blank expander for cv control, fix gif animation jitter

Diffstat:
Mplugin.json | 5+++++
Ares/ComputerscareCustomBlankExpanderPanel.svg | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Computerscare.cpp | 1+
Msrc/Computerscare.hpp | 1+
Msrc/ComputerscareBlank.cpp | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/animatedGif.hpp | 13++++++++++++-
6 files changed, 261 insertions(+), 15 deletions(-)

diff --git a/plugin.json b/plugin.json @@ -76,6 +76,11 @@ "description":"Customizable, resizable, lovable blank panel. \nLoad your own PNG, JPEG, BMP, or GIF.", "tags":["Blank","Visual"] }, + {"slug":"computerscare-blank-expander", + "name":"Custom Blank Expander", + "description":"Allows CV Control of Computerscare Custom Blank GIF Animation", + "tags":["Blank","Visual"] + }, {"slug":"computerscare-stoly-fick-pigure", "name":"Stoly Fick Pigure", "description":"Draw a stick figure", diff --git a/res/ComputerscareCustomBlankExpanderPanel.svg b/res/ComputerscareCustomBlankExpanderPanel.svg @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="30" + height="380" + viewBox="0 0 7.9374995 100.54167" + version="1.1" + id="svg8" + inkscape:version="1.0.1 (c497b03c, 2020-09-10)" + sodipodi:docname="ComputerscareCustomBlankExpanderPanel.svg"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="5.6" + inkscape:cx="18.625644" + inkscape:cy="329.86693" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + showgrid="false" + inkscape:window-width="1252" + inkscape:window-height="855" + inkscape:window-x="0" + inkscape:window-y="23" + inkscape:window-maximized="0" + units="px" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + id="rect2605" + style="fill:#e7e7e7;fill-opacity:1;stroke:#171717;stroke-width:0.19605;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 0.11351532,0.11283527 H 7.999953 L 7.952706,100.43718 0.11351532,99.96175 Z" + sodipodi:nodetypes="ccccc" /> + <g + id="g9547" + transform="matrix(0.34537853,-0.08593646,0,0.33853324,-3.4926401,0.35544723)" + style="display:inline;enable-background:new"> + <g + style="display:inline" + inkscape:label="Layer 1" + id="layer4" /> + <path + id="rect5872" + d="m 11.987289,9.3234482 h 7.991524 v 3.9957608 h -7.991524 z" + style="opacity:1;fill:#0f0f00;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:connector-curvature="0" /> + <path + id="rect5874" + d="m 14.667374,10.662096 h 3.995763 v 1.331921 h -3.995763 z" + style="opacity:1;fill:#ffffff;fill-opacity:0.985714;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:connector-curvature="0" /> + <path + id="rect5878" + d="m -29.323654,9.3234482 h 7.989551 v 3.9957608 h -7.989551 z" + style="opacity:1;fill:#0c0c00;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="scale(-1,1)" + inkscape:connector-curvature="0" /> + <path + id="rect5880" + d="m 26.64423,10.662096 -3.171232,0.209056 v 1.331921 l 3.171232,-0.209056 z" + style="opacity:1;fill:#ffffff;fill-opacity:0.985714;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:connector-curvature="0" /> + <path + id="rect5882" + d="m 19.995058,14.667374 h 1.331921 v 3.995762 h -1.331921 z" + style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:connector-curvature="0" /> + <path + id="rect5884" + d="m 20.888507,17.21991 2.579094,0.32072 v 1.33192 l -2.579094,-0.32072 z" + style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:connector-curvature="0" /> + <path + id="rect5886" + d="m 11.954803,20.011301 h 2.696328 v 2.663842 h -2.696328 z" + style="opacity:1;fill:#000200;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:connector-curvature="0" /> + <path + id="rect5888" + d="m 27.970341,20.011301 h 2.696327 v 2.663842 h -2.696327 z" + style="opacity:1;fill:#000200;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:connector-curvature="0" /> + <path + id="rect5890" + d="m 14.651131,20.011301 h 13.351695 v 1.33192 H 14.651131 Z" + style="opacity:1;fill:#000200;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/src/Computerscare.cpp b/src/Computerscare.cpp @@ -20,6 +20,7 @@ void init(Plugin *p) { p->addModel(modelComputerscareFolyPace); p->addModel(modelComputerscareBlank); + p->addModel(modelComputerscareBlankExpander); p->addModel(modelComputerscareStolyFickPigure); p->addModel(modelComputerscareGolyPenerator); diff --git a/src/Computerscare.hpp b/src/Computerscare.hpp @@ -39,6 +39,7 @@ extern Model *modelComputerscareSolyPequencer; extern Model *modelComputerscareFolyPace; extern Model *modelComputerscareStolyFickPigure; extern Model *modelComputerscareBlank; +extern Model *modelComputerscareBlankExpander; extern Model *modelComputerscareGolyPenerator; extern Model *modelComputerscareMolyPatrix; diff --git a/src/ComputerscareBlank.cpp b/src/ComputerscareBlank.cpp @@ -31,6 +31,8 @@ struct ComputerscareBlank : ComputerscareMenuParamModule { int numFrames = 0; int stepCounter = 0; float frameDelay = .5; + std::vector<float> frameDelays; + int samplesDelay = 10000; int speed = 100000; int imageStatus = 0; @@ -48,9 +50,7 @@ struct ComputerscareBlank : ComputerscareMenuParamModule { NUM_PARAMS }; enum InputIds { - NUM_INPUTS, - CLOCK_INPUT, - RESET_INPUT + NUM_INPUTS }; enum OutputIds { NUM_OUTPUTS @@ -62,7 +62,7 @@ struct ComputerscareBlank : ComputerscareMenuParamModule { ComputerscareBlank() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); - configMenuParam(ANIMATION_SPEED, 0.05f, 20.f, 1.0, "Animation Speed", 2,"x"); + configMenuParam(ANIMATION_SPEED, 0.05f, 20.f, 1.0, "Animation Speed", 2, "x"); configParam(ANIMATION_ENABLED, 0.f, 1.f, 1.f, "Animation Enabled"); configParam(CONSTANT_FRAME_DELAY, 0.f, 1.f, 0.f, "Constant Frame Delay"); configMenuParam(END_BEHAVIOR, 0.f, 5.f, 0.f, "Animation End Behavior", 2); @@ -79,8 +79,8 @@ struct ComputerscareBlank : ComputerscareMenuParamModule { endBehaviorDescriptions.push_back("Load Next"); endBehaviorDescriptions.push_back("Load Previous"); - - configMenuParam(ANIMATION_MODE, 0.f, "Animation Mode",animationModeDescriptions); + + configMenuParam(ANIMATION_MODE, 0.f, "Animation Mode", animationModeDescriptions); paths.push_back("empty"); @@ -98,6 +98,21 @@ struct ComputerscareBlank : ComputerscareMenuParamModule { } } } + if (rightExpander.module && rightExpander.module->model == modelComputerscareBlankExpander) { + // Get message from right expander + float *message = (float*) rightExpander.module->leftExpander.producerMessage; + // Write message + /*for (int i = 0; i < 8; i++) { + message[i] = inputs[i].getVoltage() / 10.f; + }*/ + if (stepCounter == 90) { + + + } + message[0] = (currentFrame == 0) ? 10.f : 0.f; + // Flip messages at the end of the timestep + rightExpander.module->leftExpander.messageFlipRequested = true; + } } void onReset() override { zoomX = 1; @@ -182,6 +197,9 @@ struct ComputerscareBlank : ComputerscareMenuParamModule { } } } + void setFrameDelays(std::vector<float> frameDelaysSeconds) { + frameDelays = frameDelaysSeconds; + } std::string getPath() { //return numFrames > 0 ? paths[currentFrame] : ""; return paths[0]; @@ -193,12 +211,13 @@ struct ComputerscareBlank : ComputerscareMenuParamModule { else { prevFrame(); } - if(currentFrame == 0) { + if (currentFrame == 0) { int eb = params[END_BEHAVIOR].getValue(); - if(eb == 3 ) { + if (eb == 3 ) { loadRandomGif(); } } + setFrameDelay(frameDelays[currentFrame]); } void nextFrame() { currentFrame++; @@ -317,7 +336,7 @@ struct KeyboardControlChildMenu : MenuItem { Menu *menu = new Menu; menu->addChild(construct<MenuLabel>(&MenuLabel::text, "A,S,D,F: Translate image position")); menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Z,X: Zoom in/out")); - menu->addChild(construct<MenuLabel>(&MenuLabel::text, "J,L: Previous / next frame")); + menu->addChild(construct<MenuLabel>(&MenuLabel::text, "J,L: Previous / next frame")); menu->addChild(construct<MenuLabel>(&MenuLabel::text, "K: Go to first frame")); menu->addChild(construct<MenuLabel>(&MenuLabel::text, "I: Go to random frame")); @@ -391,6 +410,7 @@ struct PNGDisplay : TransparentWidget { img = gifBuddy.getHandle(); blankModule->setFrameCount(gifBuddy.getFrameCount()); + blankModule->setFrameDelays(gifBuddy.getAllFrameDelaysSeconds()); blankModule->setFrameDelay(gifBuddy.getSecondsDelay(0)); blankModule->setImageStatus(gifBuddy.getImageStatus()); @@ -421,12 +441,20 @@ struct PNGDisplay : TransparentWidget { nvgFill(args.vg); nvgClosePath(args.vg); } + //if (blankModule->currentFrame != currentFrame) { + gifBuddy.displayGifFrame(args.vg, currentFrame); + //} + } + } + void step() override { + if (blankModule && blankModule->loadedJSON) { if (blankModule->currentFrame != currentFrame) { currentFrame = blankModule->currentFrame; - blankModule->setFrameDelay(gifBuddy.getSecondsDelay(currentFrame)); - gifBuddy.displayGifFrame(args.vg, currentFrame); + //blankModule->setFrameDelay(gifBuddy.getSecondsDelay(currentFrame)); + } } + TransparentWidget::step(); } }; @@ -481,7 +509,7 @@ struct ComputerscareBlankWidget : MenuParamModuleWidget { menu->addChild(new MenuEntry); //menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Keyboard Controls:")); - + KeyboardControlChildMenu *kbMenu = new KeyboardControlChildMenu(); kbMenu->text = "Keyboard Controls"; kbMenu->rightText = RIGHT_ARROW; @@ -663,4 +691,89 @@ struct ComputerscareBlankWidget : MenuParamModuleWidget { SmallLetterDisplay* smallLetterDisplay; }; -Model *modelComputerscareBlank = createModel<ComputerscareBlank, ComputerscareBlankWidget>("computerscare-blank"); -\ No newline at end of file +struct ComputerscareBlankExpander : Module { + float leftMessages[2][8] = {}; + bool isConnected = false; + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + CLOCK_INPUT, + RESET_INPUT, + SCAN_INPUT, + NUM_INPUTS + }; + enum OutputIds { + EOC_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + + dsp::SchmittTrigger eocMessageReadTrigger; + dsp::PulseGenerator eocPulse; + dsp::PulseGenerator eachFramePulse; + + + ComputerscareBlankExpander() { + + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + + leftExpander.producerMessage = leftMessages[0]; + leftExpander.consumerMessage = leftMessages[1]; + } + + void process(const ProcessArgs &args) override { + if (leftExpander.module && leftExpander.module->model == modelComputerscareBlank) { + // Get consumer message + float *message = (float*) leftExpander.consumerMessage; + isConnected = true; + + //eocMessageReadTrigger.process(message[0]); + if (eocMessageReadTrigger.process(message[0])) { + eocPulse.trigger(1e-3); + } + outputs[EOC_OUTPUT].setVoltage(eocPulse.process(args.sampleTime) ? 10.f : 0.f); + //DEBUG("HANG ON TO HIM MURPH!"); + /*for (int i = 0; i < 8; i++) { + lights[i].setBrightness(message[i]); + }*/ + } + else { + isConnected = false; + // No mother module is connected. + // TODO Clear the lights. + } + } +}; +struct ComputerscareBlankExpanderWidget : ModuleWidget { + ComputerscareBlankExpanderWidget(ComputerscareBlankExpander *module) { + setModule(module); + box.size = Vec(30, 380); + { + ComputerscareSVGPanel *panel = new ComputerscareSVGPanel(); + panel->box.size = box.size; + panel->setBackground(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ComputerscareCustomBlankExpanderPanel.svg"))); + addChild(panel); + } + + DEBUG("clock_input:%i", ComputerscareBlankExpander::CLOCK_INPUT); + + float inStartY = 30; + float dY = 40; + + float outStartY = 250; + + addInput(createInput<InPort>(Vec(2, inStartY), module, ComputerscareBlankExpander::CLOCK_INPUT)); + addInput(createInput<InPort>(Vec(2, inStartY + dY), module, ComputerscareBlankExpander::SCAN_INPUT)); + addInput(createInput<InPort>(Vec(2, inStartY + 2 * dY), module, ComputerscareBlankExpander::RESET_INPUT)); + + addOutput(createOutput<PointingUpPentagonPort>(Vec(2, outStartY), module, ComputerscareBlankExpander::EOC_OUTPUT)); + + + } +}; + +Model *modelComputerscareBlank = createModel<ComputerscareBlank, ComputerscareBlankWidget>("computerscare-blank"); +Model *modelComputerscareBlankExpander = createModel<ComputerscareBlankExpander, ComputerscareBlankExpanderWidget>("computerscare-blank-expander"); diff --git a/src/animatedGif.hpp b/src/animatedGif.hpp @@ -146,6 +146,7 @@ STBIDEF unsigned char *stbi_xload(char const *filename, int *x, int *y, int *fra struct AnimatedGifBuddy { std::vector<unsigned char*> framePointers; std::vector<int> frameDelays; + std::vector<float> frameDelaysSeconds; int imageHandle; bool initialized = false; int numFrames = -1; @@ -183,13 +184,14 @@ struct AnimatedGifBuddy { printf("image status:%i\n", imageStatus); return 0; } + updateFrameDelaysSeconds(); image = nvgCreateImageRGBA(ctx, w, h, imageFlags, img); initialized = true; return image; } void displayGifFrame(NVGcontext* ctx, int frameNumber) { - if (initialized && frameNumber < numFrames && (imageStatus == 1 && numFrames > 0)) { + if (initialized && (frameNumber < numFrames) && (imageStatus == 1 && numFrames > 0)) { const unsigned char* dataAtFrame = framePointers[frameNumber]; nvgUpdateImage(ctx, imageHandle, dataAtFrame); } @@ -207,4 +209,13 @@ struct AnimatedGifBuddy { } return secondsDelay; } + void updateFrameDelaysSeconds() { + frameDelaysSeconds.resize(0); + for (unsigned int i = 0; i < frameDelays.size(); i++) { + frameDelaysSeconds.push_back( ((float) frameDelays[i]) / 100); + } + } + std::vector<float> getAllFrameDelaysSeconds() { + return frameDelaysSeconds; + } }; \ No newline at end of file