├── CHANGELOG.md ├── LICENSE.txt ├── Makefile ├── README.md ├── RS.code-workspace ├── img ├── RSVectorVictor.png ├── boogie.png └── fido3.png ├── plugin.json ├── res ├── RSVectorVictor.svg ├── components │ ├── RSButton.svg │ ├── RSButtonInvisible.svg │ ├── RSButtonInvisibleIsh.svg │ ├── RSButtonPress.svg │ ├── RSJackMonoIn.svg │ ├── RSJackMonoOut.svg │ ├── RSJackPolyIn.svg │ ├── RSJackPolyOut.svg │ ├── RSJackSmallMonoIn.svg │ ├── RSJackSmallMonoOut.svg │ ├── RSKnobInvisible.svg │ ├── RSKnobLrg.svg │ ├── RSKnobMed.svg │ ├── RSKnobSml.svg │ ├── RSRoundButton.svg │ ├── RSRoundButtonPress.svg │ ├── RSSwitch_0.svg │ ├── RSSwitch_1.svg │ └── RSSwitch_2.svg └── fonts │ └── Ubuntu Condensed 400.ttf └── src ├── Fido3-16.cpp ├── RS.hpp ├── RSBlank.cpp ├── RSBoogieBay.cpp ├── RSBoogieBayH8.cpp ├── RSCVHeat.cpp ├── RSGroundControl.cpp ├── RSHeat.cpp ├── RSLaunchControl.cpp ├── RSMFH.cpp ├── RSMajorTom.cpp ├── RSMissionControl.cpp ├── RSModule.hpp ├── RSModuleWidgetDraw.hpp ├── RSPhaseFour.cpp ├── RSPhaseOne.cpp ├── RSReheat.cpp ├── RSScratch.cpp ├── RSShades.cpp ├── RSSkeleton.cpp ├── RSVectorVictor.cpp ├── RSXYGLR.cpp ├── plugin.cpp └── plugin.hpp /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | 27/10/19 V1.0.0 Initial version including Vector Victor, Boogie Bay & Boogie Bay H8. 4 | 27/11/19 V1.1.0 Added Heat 5 | 6 | 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # If RACK_DIR is not defined when calling the Makefile, default to two directories above 2 | RACK_DIR ?= ../.. 3 | 4 | # FLAGS will be passed to both the C and C++ compiler 5 | FLAGS += 6 | CFLAGS += 7 | CXXFLAGS += 8 | 9 | # Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. 10 | # Static libraries are fine, but they should be added to this plugin's build system. 11 | LDFLAGS += 12 | 13 | # Add .cpp files to the build 14 | SOURCES += $(wildcard src/*.cpp) 15 | 16 | # Add files to the ZIP package when running `make dist` 17 | # The compiled plugin and "plugin.json" are automatically added. 18 | DISTRIBUTABLES += res 19 | DISTRIBUTABLES += $(wildcard LICENSE*) 20 | 21 | # Include the Rack plugin Makefile framework 22 | include $(RACK_DIR)/plugin.mk 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Racket Science 2 | Modules for the VCV Rack virtual modular synthesizer. 3 | 4 | ## PHYDEAUX III 5 | ![](./img/fido3.png) 6 | 7 | I know, yet another bloody step sequencer, in fact it nearly got called YABSS. I built this as an exercise and as a result feel much more confident developing for VCV now. It's the size of a bus so I named it after one of Frank Zappa's. 8 | 9 | You get four rows of sixteen steps with pulses and gates for each step, each row also has an attenuverter and scaled CV output. There are currently 276 parameters, 71 inputs, 148 outputs and 64 blinkenlights. In order to fit all of that into a meagre 104hp there are stealth sockets that only appear when you drag a cable, every pulse & gate has it's own output and the buttons on the left also double up as inputs. There are CV & write inputs, you can record a sequence from a midi keyboard should you wish. Outputs can be fed back into inputs so, for instance, if you want a row to be eight steps long, set a pulse on the ninth step and feed that back to the index reset input for that row. If you want to modify steps on the fly, you could take a gate output into write in. Similarly you can play back multiple patterns by feeding an EOC output back to the next pattern input. You could use pulses from one row to step another row etc etc. The possibilities are endless really. 10 | 11 | There are also phase inputs so it can be driven by modules such as ZZC and run backwards, forwards, upside down & inside out if you like. 12 | 13 | 14 | ## Boogie Bay 15 | 16 | ![](./img/boogie.png) 17 | 18 | You've seen flying faders, with Boogie Bay you have sliding sockets for all of your wire wobbling needs. 19 | These modules also double up as voltage indicators, right click on the vertical 2 channel BB for range menu, BBH8 range can be selected for each channel by clicking on the individual voltage labels, also BBH8 has invisible scribble strips, click on an underscore to enter text to describe each channel. 20 | 21 | ## Vector Victor 22 | 23 | Shortly after I first got into VCV around May 2019 I found myself looking for a real time CV loop recorder yet couldn't find one, once the VCV Prototype module appeared I created a simple precursor of Vector Victor, also inspired by the ZZC phase based way of timing. 24 | 25 | Vector Victor is a phase driven CV recorder, if you're familiar with programming think of it as a thousand element array indexed by the phase input as that's exactly what it is. You get two for the price of one! 26 | 27 | Typically you would feed the phase input with a slow rising sawtooth from a LFO, or ZZC clock & divider modules to keep everything in sync. A simple use case is shown below where Vector Victor loops input from a MIDI keyboard. It can also be used to record knob movements or whatever really but is not designed with audio rates in mind. 28 | 29 | ![](./img/RSVectorVictor.png) 30 | 31 | 32 | ## Notices 33 | 34 | **Racket Science** is copyright © 2020 Ewen Bates, specifically: 35 | 36 | The **brand name** **Racket Science**. If you want to use this for a racket sports brand just ask, but in the context of creations that make a noise (aka racket) with synthesis, this is mine. 37 | 38 | All **source code** is copyright © 2020 Ewen Bates and is licensed under the [GNU General Public License v3.0](gpl-3.0.txt). 39 | 40 | All **graphics** in the `res` directory are copyright © 2020 Ewen Bates and licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). 41 | 42 | Many thanks to the VCV developer community. 43 | -------------------------------------------------------------------------------- /RS.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "/home/ewen/Rack-SDK" 8 | } 9 | ], 10 | "settings": { 11 | "files.associations": { 12 | "atomic": "cpp", 13 | "memory_resource": "cpp", 14 | "*.tcc": "cpp", 15 | "tuple": "cpp" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /img/RSVectorVictor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ContemporaryInsanity/RacketScience/2978e546f55cd3ad7cdb6c03f7e003b8351bbd5f/img/RSVectorVictor.png -------------------------------------------------------------------------------- /img/boogie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ContemporaryInsanity/RacketScience/2978e546f55cd3ad7cdb6c03f7e003b8351bbd5f/img/boogie.png -------------------------------------------------------------------------------- /img/fido3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ContemporaryInsanity/RacketScience/2978e546f55cd3ad7cdb6c03f7e003b8351bbd5f/img/fido3.png -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "RacketScience", 3 | "name": "Racket Science", 4 | "version": "1.2.0", 5 | "license": "GPL-3.0", 6 | "brand": "Racket Science", 7 | "author": "ContemporaryInsanity", 8 | "authorEmail": "ContemporaryInsanity@gmail.com", 9 | "authorUrl": "https://github.com/ContemporaryInsanity", 10 | "pluginUrl": "https://github.com/ContemporaryInsanity/RacketScience", 11 | "manualUrl": "https://github.com/ContemporaryInsanity/RacketScience", 12 | "sourceUrl": "https://github.com/ContemporaryInsanity/RacketScience", 13 | "donateUrl": "https://www.paypal.me/EwenBates", 14 | "modules": [ 15 | { 16 | "slug": "RSGroundControl", 17 | "name": "RSGroundControl", 18 | "description": "Theme settings", 19 | "tags": [ 20 | "Visual", 21 | "Utility" 22 | ] 23 | }, 24 | { 25 | "slug": "RSMajorTom", 26 | "name": "RSMajorTom", 27 | "description": "Colour cycling", 28 | "tags": [ 29 | "Utility" 30 | ] 31 | }, 32 | { 33 | "slug": "RSHeat", 34 | "name": "RSHeat", 35 | "description": "Note / Octave Heat Map", 36 | "tags": [ 37 | "Visual", 38 | "Utility" 39 | ] 40 | }, 41 | { 42 | "slug": "RSReheat", 43 | "name": "RSReheat", 44 | "description": "Note / Octave Heat Map", 45 | "tags": [ 46 | "Visual", 47 | "Utility" 48 | ] 49 | }, 50 | { 51 | "slug": "RSCVHeat", 52 | "name": "RSCVHeat", 53 | "description": "CV Heat Map", 54 | "tags": [ 55 | "Visual", 56 | "Utility" 57 | ] 58 | }, 59 | { 60 | "slug": "RSBoogieBay", 61 | "name": "RSBoogieBay", 62 | "description": "Animated patchbay / voltage indicator", 63 | "tags": [ 64 | "Visual", 65 | "Utility" 66 | ] 67 | }, 68 | { 69 | "slug": "RSBoogieBayH8", 70 | "name": "RSBoogieBayH8", 71 | "description": "Animated patchbay / voltage indicator", 72 | "tags": [ 73 | "Visual", 74 | "Utility" 75 | ] 76 | }, 77 | { 78 | "slug": "RSMFH", 79 | "name": "RSMFH", 80 | "description": "Utility", 81 | "tags": [ 82 | "Utility" 83 | ] 84 | }, 85 | { 86 | "slug": "RSBlank", 87 | "name": "RSBlank", 88 | "description": "Resizeable blank panel", 89 | "tags": [ 90 | "Visual", 91 | "Utility" 92 | ] 93 | }, 94 | { 95 | "slug": "RSShades", 96 | "name": "RSShades", 97 | "description": "Colour Control", 98 | "tags": [ 99 | "Visual", 100 | "Utility" 101 | ] 102 | }, 103 | { 104 | "slug": "RSFido316", 105 | "name": "RSFido316", 106 | "description": "16 Step Sequencer", 107 | "tags": [ 108 | "Sequencer" 109 | ] 110 | }, 111 | { 112 | "slug": "RSPhaseOne", 113 | "name": "RSPhaseOne", 114 | "description": "Phase Recorder / Step Sequencer", 115 | "tags": [ 116 | "Recorder", 117 | "Sequencer" 118 | ] 119 | }, 120 | { 121 | "slug": "RSPhaseFour", 122 | "name": "RSPhaseFour", 123 | "description": "Phase Recorder / Step Sequencer", 124 | "tags": [ 125 | "Recorder", 126 | "Sequencer" 127 | ] 128 | }, 129 | { 130 | "slug": "RSMissionControl", 131 | "name": "RSMissionControl", 132 | "description": "Seuqncer Launcher Sequencer", 133 | "tags": [ 134 | "Utility", 135 | "Sequencer" 136 | ] 137 | }, 138 | { 139 | "slug": "RSLaunchControl", 140 | "name": "RSLaunchControl", 141 | "description": "Phase Sequencer Launcher", 142 | "tags": [ 143 | "Utility", 144 | "Sequencer" 145 | ] 146 | }, 147 | { 148 | "slug": "RSXYGLR", 149 | "name": "RSXYGLR", 150 | "description": "XY Pad Loop Recorder", 151 | "tags": [ 152 | "Recorder", 153 | "Sequencer" 154 | ] 155 | }, 156 | { 157 | "slug": "RSVectorVictor", 158 | "name": "RSVectorVictor", 159 | "description": "Phase Driven CV Looper", 160 | "tags": [ 161 | "Recorder", 162 | "Sequencer" 163 | ] 164 | } 165 | ] 166 | } -------------------------------------------------------------------------------- /res/components/RSButton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 65 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /res/components/RSButtonInvisible.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /res/components/RSButtonInvisibleIsh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 65 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /res/components/RSButtonPress.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 45 | 47 | 48 | 50 | image/svg+xml 51 | 53 | 54 | 55 | 56 | 57 | 62 | 66 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /res/components/RSJackMonoIn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 30 | 31 | 60 | 69 | 70 | 72 | 73 | 75 | image/svg+xml 76 | 78 | 79 | 80 | 81 | 82 | 87 | 90 | 96 | 103 | 104 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /res/components/RSJackMonoOut.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 30 | 31 | 59 | 68 | 69 | 71 | 72 | 74 | image/svg+xml 75 | 77 | 78 | 79 | 80 | 81 | 86 | 89 | 95 | 102 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /res/components/RSJackPolyIn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 30 | 31 | 58 | 67 | 68 | 70 | 71 | 73 | image/svg+xml 74 | 76 | 77 | 78 | 79 | 80 | 85 | 88 | 94 | 101 | 102 | 109 | 116 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /res/components/RSJackPolyOut.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 30 | 31 | 60 | 69 | 70 | 72 | 73 | 75 | image/svg+xml 76 | 78 | 79 | 80 | 81 | 82 | 87 | 91 | 97 | 104 | 111 | 118 | 125 | 126 | 130 | 136 | 143 | 150 | 157 | 164 | 165 | 169 | 175 | 182 | 189 | 196 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /res/components/RSJackSmallMonoIn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 30 | 31 | 58 | 67 | 68 | 70 | 71 | 73 | image/svg+xml 74 | 76 | 77 | 78 | 79 | 80 | 85 | 88 | 95 | 102 | 103 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /res/components/RSJackSmallMonoOut.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 30 | 31 | 58 | 67 | 68 | 70 | 71 | 73 | image/svg+xml 74 | 76 | 77 | 78 | 79 | 80 | 85 | 88 | 95 | 102 | 103 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /res/components/RSKnobInvisible.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /res/components/RSKnobLrg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 50 | 59 | 60 | 62 | 63 | 65 | image/svg+xml 66 | 68 | 69 | 70 | 71 | 72 | 77 | 83 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /res/components/RSKnobMed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 50 | 59 | 60 | 62 | 63 | 65 | image/svg+xml 66 | 68 | 69 | 70 | 71 | 72 | 77 | 84 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /res/components/RSKnobSml.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 50 | 59 | 60 | 62 | 63 | 65 | image/svg+xml 66 | 68 | 69 | 70 | 71 | 72 | 77 | 83 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /res/components/RSRoundButton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 65 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /res/components/RSRoundButtonPress.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 45 | 47 | 48 | 50 | image/svg+xml 51 | 53 | 54 | 55 | 56 | 57 | 62 | 66 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /res/components/RSSwitch_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 74 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /res/components/RSSwitch_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 74 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /res/components/RSSwitch_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 74 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /res/fonts/Ubuntu Condensed 400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ContemporaryInsanity/RacketScience/2978e546f55cd3ad7cdb6c03f7e003b8351bbd5f/res/fonts/Ubuntu Condensed 400.ttf -------------------------------------------------------------------------------- /src/RS.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "RSModule.hpp" 6 | 7 | 8 | #define quantize(v) (round(v * 12.f) / 12.f) 9 | #define octave(v) int(floor(quantize(v))) 10 | #define note(v) (int(round((v + 10) * 12)) % 12) 11 | 12 | static float noteVoltage[] = { 13 | 0.00000f, // C 14 | 0.08333f, // C# 15 | 0.16667f, // D 16 | 0.25000f, // D# 17 | 0.33333f, // E 18 | 0.41667f, // F 19 | 0.50000f, // F# 20 | 0.58333f, // G 21 | 0.66667f, // G# 22 | 0.75000f, // A 23 | 0.83333f, // A# 24 | 0.91667f, // B 25 | }; 26 | 27 | static inline float RSclamp(float in, float min, float max) { 28 | return in < min ? min : (in > max ? max : in); 29 | } 30 | 31 | static inline float RSscale(float in, float inMin, float inMax, float outMin, float outMax) { 32 | return((outMax - outMin) * (in - inMin) / (inMax - inMin)) + outMin; 33 | } 34 | 35 | 36 | /* 37 | Racket Science custom components 38 | (C) 2020 Ewen Bates 39 | */ 40 | 41 | // Colours 42 | 43 | #define COLOR_RS_GREY nvgRGB(0xb4, 0xb4, 0xb4) 44 | #define COLOR_RS_BRONZE nvgRGB(0x85, 0x87, 0x39) 45 | #define COLOR_BLACK nvgRGB(0x00, 0x00, 0x00) 46 | #define COLOR_RED nvgRGB(0xff, 0x00, 0x00) 47 | #define COLOR_GREEN nvgRGB(0x00, 0xff, 0x00) 48 | #define COLOR_BLUE nvgRGB(0x00, 0x00, 0xff) 49 | #define COLOR_YELLOW nvgRGB(0xff, 0xff, 0x00) 50 | #define COLOR_WHITE nvgRGB(0xff, 0xff, 0xff) 51 | 52 | 53 | // LEDs 54 | 55 | struct RSLightWidget : LightWidget { 56 | 57 | 58 | 59 | 60 | 61 | }; 62 | 63 | 64 | // Labels 65 | 66 | // Uses own colour, intended for use with scale labels, green for pos, red for neg 67 | // Now we have themes this causes problems with contrast, perhaps have a black background? 68 | struct RSLabel : LedDisplay { 69 | int fontSize; 70 | std::shared_ptr font; 71 | std::string text; 72 | NVGcolor color; 73 | 74 | RSLabel(int x, int y, const char* str = "", int fontSize = 10, const NVGcolor& colour = COLOR_RS_GREY) { 75 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/Ubuntu Condensed 400.ttf")); 76 | box.pos = Vec(x, y); 77 | box.size = Vec(120, 12); 78 | text = str; 79 | color = colour; 80 | this->fontSize = fontSize; 81 | } 82 | 83 | void draw(const DrawArgs &args) override { 84 | if(font->handle >= 0) { 85 | bndSetFont(font->handle); 86 | 87 | nvgFontSize(args.vg, fontSize); 88 | nvgFontFaceId(args.vg, font->handle); 89 | nvgTextLetterSpacing(args.vg, 0); 90 | 91 | nvgBeginPath(args.vg); 92 | nvgFillColor(args.vg, color); 93 | nvgText(args.vg, 0, 0, text.c_str(), NULL); 94 | nvgStroke(args.vg); 95 | 96 | bndSetFont(APP->window->uiFont->handle); 97 | } 98 | } 99 | }; 100 | 101 | 102 | // Uses RSGlobal color, intended for general labels 103 | struct RSLabelCentered : LedDisplay { 104 | int fontSize; 105 | std::shared_ptr font; 106 | std::string text; 107 | int *themeIdx = NULL; 108 | 109 | RSLabelCentered(int x, int y, const char* str = "", int fontSize = 10, RSModule *module = NULL) { 110 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/Ubuntu Condensed 400.ttf")); 111 | this->fontSize = fontSize; 112 | box.pos = Vec(x, y); 113 | text = str; 114 | 115 | if(module) themeIdx = &(module->RSTheme); 116 | } 117 | 118 | void draw(const DrawArgs &args) override { 119 | if(font->handle >= 0) { 120 | bndSetFont(font->handle); 121 | 122 | nvgFontSize(args.vg, fontSize); 123 | nvgFontFaceId(args.vg, font->handle); 124 | nvgTextLetterSpacing(args.vg, 0); 125 | nvgTextAlign(args.vg, NVG_ALIGN_CENTER); 126 | 127 | nvgBeginPath(args.vg); 128 | if(themeIdx == NULL) nvgFillColor(args.vg, RSGlobal.themes[RSGlobal.themeIdx].lbColor); 129 | else nvgFillColor(args.vg, RSGlobal.themes[*themeIdx - 1].lbColor); 130 | 131 | nvgText(args.vg, 0, 0, text.c_str(), NULL); 132 | nvgStroke(args.vg); 133 | 134 | bndSetFont(APP->window->uiFont->handle); 135 | } 136 | } 137 | }; 138 | 139 | 140 | // Scribble Strips 141 | 142 | struct RSScribbleStrip : LedDisplayTextField { 143 | int textSize = 12; 144 | int numChars = 25; 145 | 146 | RSScribbleStrip(int x, int y, int size = 150, const char* str = "_") { 147 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/Ubuntu Condensed 400.ttf")); 148 | box.pos = Vec(x, y); 149 | box.size = Vec(size, 14); // Derive size from pos & panel width? have numChars as parameter instead 150 | textOffset = Vec(0, -3); 151 | multiline = false; // Doesn't appear to have the desired effect 152 | text = str; 153 | } 154 | 155 | // We want scribbles without background 156 | void draw(const DrawArgs &args) override { 157 | if(cursor > numChars) { 158 | text.resize(numChars); 159 | cursor = numChars; 160 | selection = numChars; 161 | } 162 | 163 | //nvgScissor(args.vg, RECT_ARGS(args.clipBox)); 164 | if (font->handle >= 0) { 165 | bndSetFont(font->handle); 166 | 167 | float bounds[4]; 168 | nvgTextBounds(args.vg, 0.0f, 0.0f, text.c_str(), NULL, bounds); 169 | float textWidth = bounds[2]; 170 | 171 | // If we subtract textWidth / 2 from parameter 2 textOffset.x we get dynamically centered strips 172 | // however the mouse doesn't position the cursor accordingly 173 | NVGcolor color = RSGlobal.themes[RSGlobal.themeIdx].ssColor; 174 | NVGcolor highlightColor = color; 175 | highlightColor.a = 0.5; 176 | int begin = std::min(cursor, selection); 177 | int end = (this == APP->event->selectedWidget) ? std::max(cursor, selection) : -1; 178 | //INFO("Racket Science: textWidth: %f box.size.x: %f box.size.y: %f", textWidth, box.size.x, box.size.y); 179 | //INFO("Racket Science: cursor: %i selection: %i begin: %i end: %i", cursor, selection, begin, end); 180 | bndIconLabelCaret(args.vg, textOffset.x, textOffset.y, 181 | box.size.x - 2*textOffset.x, box.size.y - 2*textOffset.y, 182 | -1, color, textSize, text.c_str(), highlightColor, begin, end); 183 | 184 | bndSetFont(APP->window->uiFont->handle); 185 | } 186 | //nvgResetScissor(args.vg); 187 | } 188 | }; 189 | 190 | 191 | // Ports 192 | 193 | struct RSJackMonoOut : SVGPort { RSJackMonoOut() { setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackMonoOut.svg"))); } }; 194 | struct RSJackSmallMonoOut : SVGPort { RSJackSmallMonoOut() { setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackSmallMonoOut.svg"))); } }; 195 | struct RSJackPolyOut : SVGPort { RSJackPolyOut() { setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackPolyOut.svg"))); } }; 196 | struct RSJackMonoIn : SVGPort { RSJackMonoIn() { setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackMonoIn.svg"))); } }; 197 | struct RSJackSmallMonoIn : SVGPort { RSJackSmallMonoIn() { setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackSmallMonoIn.svg"))); } }; 198 | struct RSJackPolyIn : SVGPort { RSJackPolyIn() { setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackPolyIn.svg"))); } }; 199 | 200 | struct RSStealthJackIn : app::SvgPort { // With thanks to https://github.com/DominoMarama/ReTunesFree 201 | // RSStealthJackIn() { 202 | // setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackMonoIn.svg"))); 203 | // } 204 | 205 | void step() override { 206 | if(!module) return; 207 | 208 | if(module->inputs[portId].isConnected()) { 209 | Widget::show(); 210 | } 211 | else { 212 | CableWidget* cw = APP->scene->rack->incompleteCable; 213 | if(cw) { 214 | if(cw->outputPort) Widget::show(); 215 | else Widget::hide(); 216 | } 217 | else Widget::hide(); 218 | } 219 | Widget::step(); 220 | } 221 | }; 222 | 223 | struct RSStealthJackOut : app::SvgPort { 224 | // RSStealthJackOut() { 225 | // setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackMonoOut.svg"))); 226 | // } 227 | 228 | void step() override { 229 | if(!module) return; 230 | 231 | if(module->outputs[portId].isConnected()) { 232 | Widget::show(); 233 | } 234 | else { 235 | CableWidget* cw = APP->scene->rack->incompleteCable; 236 | if(cw) { 237 | if(cw->inputPort) Widget::show(); 238 | else Widget::hide(); 239 | } 240 | else Widget::hide(); 241 | } 242 | Widget::step(); 243 | } 244 | }; 245 | 246 | struct RSStealthJackMonoIn : RSStealthJackIn { 247 | RSStealthJackMonoIn() { 248 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackMonoIn.svg"))); 249 | } 250 | }; 251 | 252 | struct RSStealthJackSmallMonoIn : RSStealthJackIn { 253 | RSStealthJackSmallMonoIn() { 254 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackSmallMonoIn.svg"))); 255 | } 256 | }; 257 | 258 | struct RSStealthJackMonoOut : RSStealthJackOut { 259 | RSStealthJackMonoOut() { 260 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackMonoOut.svg"))); 261 | } 262 | }; 263 | 264 | struct RSStealthJackSmallMonoOut : RSStealthJackOut { 265 | RSStealthJackSmallMonoOut() { 266 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackSmallMonoOut.svg"))); 267 | } 268 | }; 269 | 270 | struct RSStealthJackPolyIn : RSStealthJackIn { 271 | RSStealthJackPolyIn() { 272 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackPolyIn.svg"))); 273 | } 274 | }; 275 | 276 | struct RSStealthJackPolyOut : RSStealthJackOut { 277 | RSStealthJackPolyOut() { 278 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSJackPolyOut.svg"))); 279 | } 280 | }; 281 | 282 | 283 | // Knobs 284 | 285 | struct RSKnob : SVGKnob { 286 | RSKnob() { 287 | minAngle = -0.83 * M_PI; 288 | maxAngle = 0.83 * M_PI; 289 | 290 | shadow->opacity = 0.0f; // Hide shadows 291 | } 292 | }; 293 | 294 | struct RSKnobDetent : RSKnob { 295 | RSKnobDetent() { 296 | snap = true; 297 | } 298 | }; 299 | 300 | struct RSKnobSml : RSKnob { RSKnobSml() {setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSKnobSml.svg"))); } }; 301 | struct RSKnobMed : RSKnob { RSKnobMed() {setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSKnobMed.svg"))); } }; 302 | struct RSKnobLrg : RSKnob { RSKnobLrg() {setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSKnobLrg.svg"))); } }; 303 | 304 | struct RSKnobInvisible : RSKnob { RSKnobInvisible() {setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSKnobInvisible.svg"))); } }; 305 | 306 | struct RSKnobDetentSml : RSKnobDetent { RSKnobDetentSml() { setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSKnobSml.svg"))); } }; 307 | struct RSKnobDetentMed : RSKnobDetent { RSKnobDetentMed() { setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSKnobMed.svg"))); } }; 308 | struct RSKnobDetentLrg : RSKnobDetent { RSKnobDetentLrg() { setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSKnobLrg.svg"))); } }; 309 | 310 | struct RSKnobDetentInvisible : RSKnobDetent { RSKnobDetentInvisible() {setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSKnobInvisible.svg"))); } }; 311 | 312 | 313 | // Buttons 314 | 315 | struct RSButton : SVGSwitch { 316 | RSButton() { 317 | shadow->opacity = 0.0f; // Hide shadows 318 | } 319 | 320 | void randomize() override { 321 | SVGSwitch::randomize(); 322 | 323 | if(paramQuantity->getValue() > 0.5f) paramQuantity->setValue(1.0f); 324 | else paramQuantity->setValue(0.0f); 325 | } 326 | }; 327 | 328 | struct RSButtonToggle : RSButton { 329 | RSButtonToggle() { 330 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSButton.svg"))); 331 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSButtonPress.svg"))); 332 | } 333 | }; 334 | 335 | struct RSRoundButtonToggle : RSButton { 336 | RSRoundButtonToggle() { 337 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSRoundButton.svg"))); 338 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSRoundButtonPress.svg"))); 339 | } 340 | }; 341 | 342 | struct RSButtonToggleInvisible : RSButton { 343 | RSButtonToggleInvisible() { 344 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSButtonInvisibleIsh.svg"))); 345 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSButtonInvisible.svg"))); 346 | } 347 | }; 348 | 349 | struct RSButtonMomentary : RSButtonToggle { 350 | RSButtonMomentary() { 351 | momentary = true; 352 | } 353 | }; 354 | 355 | struct RSRoundButtonMomentary : RSRoundButtonToggle { 356 | RSRoundButtonMomentary() { 357 | momentary = true; 358 | } 359 | }; 360 | 361 | struct RSButtonMomentaryInvisible : RSButtonToggleInvisible { 362 | RSButtonMomentaryInvisible() { 363 | momentary = true; 364 | } 365 | }; 366 | 367 | // Switches 368 | 369 | struct RSSwitch2P : SVGSwitch { 370 | RSSwitch2P() { 371 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSSwitch_0.svg"))); 372 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSSwitch_2.svg"))); 373 | 374 | shadow->opacity = 0.0f; 375 | } 376 | 377 | void onChange(const event::Change &e) override { 378 | SVGSwitch::onChange(e); 379 | 380 | if(paramQuantity->getValue() > 0.5f) paramQuantity->setValue(1.0f); 381 | else paramQuantity->setValue(0.0f); 382 | } 383 | 384 | void randomize() override { 385 | SVGSwitch::randomize(); 386 | 387 | if(paramQuantity->getValue() > 0.5f) paramQuantity->setValue(1.0f); 388 | else paramQuantity->setValue(0.0f); 389 | } 390 | }; 391 | 392 | struct RSSwitch3PV : SVGSwitch { 393 | RSSwitch3PV() { 394 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSSwitch_0.svg"))); 395 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSSwitch_1.svg"))); 396 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/components/RSSwitch_2.svg"))); 397 | 398 | shadow->opacity = 0.0f; 399 | } 400 | 401 | void onChange(const event::Change &e) override { 402 | SVGSwitch::onChange(e); 403 | 404 | if(paramQuantity->getValue() > 1.33f) paramQuantity->setValue(2.0f); 405 | else if(paramQuantity->getValue() > 0.67f) paramQuantity->setValue(1.0f); 406 | else paramQuantity->setValue(0.0f); 407 | } 408 | 409 | void randomize() override { 410 | SVGSwitch::randomize(); 411 | if(paramQuantity->getValue() > 1.33f) paramQuantity->setValue(2.0f); 412 | else if(paramQuantity->getValue() > 0.67f) paramQuantity->setValue(1.0f); 413 | else paramQuantity->setValue(0.0f); 414 | } 415 | }; 416 | 417 | struct ModuleResizeHandle : OpaqueWidget { 418 | bool right = false; 419 | Vec dragPos; 420 | Rect originalBox; 421 | 422 | ModuleResizeHandle() { 423 | box.size = Vec(1 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 424 | } 425 | 426 | void onDragStart(const event::DragStart &e) override { 427 | if (e.button != GLFW_MOUSE_BUTTON_LEFT) 428 | return; 429 | 430 | dragPos = APP->scene->rack->mousePos; 431 | ModuleWidget *mw = getAncestorOfType(); 432 | assert(mw); 433 | originalBox = mw->box; 434 | } 435 | 436 | void onDragMove(const event::DragMove &e) override { 437 | ModuleWidget *mw = getAncestorOfType(); 438 | assert(mw); 439 | 440 | Vec newDragPos = APP->scene->rack->mousePos; 441 | float deltaX = newDragPos.x - dragPos.x; 442 | 443 | Rect newBox = originalBox; 444 | Rect oldBox = mw->box; 445 | const float minWidth = 3 * RACK_GRID_WIDTH; 446 | if (right) { 447 | newBox.size.x += deltaX; 448 | newBox.size.x = std::fmax(newBox.size.x, minWidth); 449 | newBox.size.x = std::round(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; 450 | } 451 | else { 452 | newBox.size.x -= deltaX; 453 | newBox.size.x = std::fmax(newBox.size.x, minWidth); 454 | newBox.size.x = std::round(newBox.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; 455 | newBox.pos.x = originalBox.pos.x + originalBox.size.x - newBox.size.x; 456 | } 457 | 458 | mw->box = newBox; 459 | if (!APP->scene->rack->requestModulePos(mw, newBox.pos)) { 460 | mw->box = oldBox; 461 | } 462 | } 463 | }; 464 | -------------------------------------------------------------------------------- /src/RSBlank.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | // struct BlankPanel : Widget { 6 | // Widget* panelBorder; 7 | 8 | // BlankPanel() { 9 | // panelBorder = new PanelBorder; 10 | // addChild(panelBorder); 11 | // } 12 | 13 | // void step() override { 14 | // panelBorder->box.size = box.size; 15 | // Widget::step(); 16 | // } 17 | 18 | // void draw(const DrawArgs& args) { 19 | // nvgBeginPath(args.vg); 20 | // nvgRoundedRect(args.vg, 0.0, 0.0, box.size.x, box.size.y, 5); 21 | // nvgFillColor(args.vg, nvgRGB(0xe6, 0xe6, 0xe6)); 22 | // nvgFill(args.vg); 23 | // Widget::draw(args); 24 | // } 25 | // }; 26 | 27 | struct RSBlank : RSModule { 28 | enum ParamIds { 29 | THEME_KNOB, 30 | NUM_PARAMS 31 | }; 32 | enum InputIds { 33 | NUM_INPUTS 34 | }; 35 | enum OutputIds { 36 | NUM_OUTPUTS 37 | }; 38 | enum LightIds { 39 | NUM_LIGHTS 40 | }; 41 | 42 | dsp::BooleanTrigger themeTrigger; 43 | 44 | RSBlank() { 45 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 46 | 47 | configParam(THEME_KNOB, 1.f, 16.f, 1.f, "THEME"); 48 | } 49 | 50 | void process(const ProcessArgs &args) override { 51 | 52 | } 53 | 54 | json_t* dataToJson() override { 55 | json_t* rootJ = json_object(); 56 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 57 | 58 | return rootJ; 59 | } 60 | 61 | void dataFromJson(json_t* rootJ) override { 62 | json_t* themeJ = json_object_get(rootJ, "theme"); 63 | if(themeJ) RSTheme = json_integer_value(themeJ); 64 | } 65 | }; 66 | 67 | struct RSBlankWidget : ModuleWidget { 68 | RSBlank *module; 69 | Widget *rightHandle; 70 | 71 | RSBlankWidget(RSBlank *module) { 72 | INFO("Racket Science: RSBlankWidget()"); 73 | 74 | setModule(module); 75 | this->module = module; 76 | 77 | box.size = Vec(RACK_GRID_WIDTH * 3, RACK_GRID_HEIGHT); 78 | int middle = box.size.x / 2 + 1; 79 | 80 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSBlank::THEME_KNOB)); 81 | 82 | addChild(new RSLabelCentered(middle, box.size.y - 15, "Racket", 12, module)); 83 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Science", 12, module)); 84 | 85 | ModuleResizeHandle *rightHandle = new ModuleResizeHandle; 86 | rightHandle->right = true; 87 | this->rightHandle = rightHandle; 88 | addChild(rightHandle); 89 | } 90 | 91 | void step() override { 92 | rightHandle->box.pos.x = box.size.x - rightHandle->box.size.x; 93 | 94 | if(module) module->RSTheme = module->params[RSBlank::THEME_KNOB].getValue(); 95 | 96 | ModuleWidget::step(); 97 | } 98 | 99 | void customDraw(const DrawArgs& args) {} 100 | #include "RSModuleWidgetDraw.hpp" 101 | 102 | json_t *toJson() override { 103 | json_t* rootJ = ModuleWidget::toJson(); 104 | 105 | json_object_set_new(rootJ, "width", json_real(box.size.x)); 106 | 107 | return rootJ; 108 | } 109 | 110 | void fromJson(json_t* rootJ) override { 111 | ModuleWidget::fromJson(rootJ); 112 | 113 | json_t* widthJ = json_object_get(rootJ, "width"); 114 | if(widthJ) box.size.x = json_number_value(widthJ); 115 | } 116 | }; 117 | 118 | Model* modelRSBlank = createModel("RSBlank"); -------------------------------------------------------------------------------- /src/RSBoogieBay.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSBoogieBay : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | INA_INPUT, 12 | INB_INPUT, 13 | NUM_INPUTS 14 | }; 15 | enum OutputIds { 16 | OUTA_OUTPUT, 17 | OUTB_OUTPUT, 18 | NUM_OUTPUTS 19 | }; 20 | enum LightIds { 21 | NUM_LIGHTS 22 | }; 23 | 24 | dsp::BooleanTrigger themeTrigger; 25 | 26 | int vrangea = 4; 27 | int vrangeb = 2; 28 | 29 | bool menuaChanged = true; 30 | bool menubChanged = true; 31 | 32 | RSBoogieBay() { 33 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 34 | 35 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 36 | } 37 | 38 | void process(const ProcessArgs &args) override { 39 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 40 | RSTheme++; 41 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 42 | } 43 | 44 | outputs[OUTA_OUTPUT].setVoltage(inputs[INA_INPUT].getVoltage()); 45 | outputs[OUTB_OUTPUT].setVoltage(inputs[INB_INPUT].getVoltage()); 46 | } 47 | 48 | void onReset() override { 49 | } 50 | 51 | json_t* dataToJson() override { 52 | json_t* rootJ = json_object(); 53 | 54 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 55 | 56 | json_object_set_new(rootJ, "vrangea", json_integer(vrangea)); 57 | json_object_set_new(rootJ, "vrangeb", json_integer(vrangeb)); 58 | 59 | return rootJ; 60 | } 61 | 62 | void dataFromJson(json_t* rootJ) override { 63 | json_t* themeJ = json_object_get(rootJ, "theme"); 64 | 65 | if(themeJ) RSTheme = json_integer_value(themeJ); 66 | 67 | json_t* vrangeaJ = json_object_get(rootJ, "vrangea"); 68 | json_t* vrangebJ = json_object_get(rootJ, "vrangeb"); 69 | 70 | if(vrangeaJ) vrangea = json_integer_value(vrangeaJ); 71 | if(vrangebJ) vrangeb = json_integer_value(vrangebJ); 72 | } 73 | }; 74 | 75 | 76 | struct RSBoogieBayWidget : ModuleWidget { 77 | RSBoogieBay* module; 78 | 79 | PortWidget *ina, *inb; 80 | 81 | RSLabel* topScaleaLabel; 82 | RSLabel* midScaleaLabel; 83 | RSLabel* botScaleaLabel; 84 | 85 | RSLabel* topScalebLabel; 86 | RSLabel* midScalebLabel; 87 | RSLabel* botScalebLabel; 88 | 89 | RSBoogieBayWidget(RSBoogieBay *module) { 90 | INFO("Racket Science: RSBoogieBayWidget()"); 91 | 92 | setModule(module); 93 | this->module = module; 94 | 95 | box.size.x = mm2px(5.08 * 5); 96 | int middle = box.size.x / 2 + 1; 97 | 98 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSBoogieBay::THEME_BUTTON)); 99 | 100 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "BOOGIE", 14, module)); 101 | addChild(new RSLabelCentered(middle, box.pos.y + 25, "BAY", 14, module)); 102 | 103 | addChild(new RSLabelCentered(box.size.x / 2, box.size.y - 6, "Racket Science", 12, module)); 104 | 105 | ina = createInputCentered(Vec(22, 175), module, RSBoogieBay::INA_INPUT); 106 | addInput(ina); 107 | inb = createInputCentered(Vec(52, 175), module, RSBoogieBay::INB_INPUT); 108 | addInput(inb); 109 | 110 | addOutput(createOutputCentered(Vec(22, 345), module, RSBoogieBay::OUTA_OUTPUT)); 111 | addOutput(createOutputCentered(Vec(52, 345), module, RSBoogieBay::OUTB_OUTPUT)); 112 | 113 | addChild(topScaleaLabel = new RSLabel(3, 58, "0", 10, COLOR_GREEN)); 114 | addChild(midScaleaLabel = new RSLabel(3, 177, "0", 10, COLOR_RS_GREY)); 115 | addChild(botScaleaLabel = new RSLabel(3, 296, "0", 10, COLOR_RED)); 116 | 117 | addChild(topScalebLabel = new RSLabel(67, 58, "0", 10, COLOR_GREEN)); 118 | addChild(midScalebLabel = new RSLabel(67, 177, "0", 10, COLOR_RS_GREY)); 119 | addChild(botScalebLabel = new RSLabel(67, 296, "0", 10, COLOR_RED)); 120 | } 121 | 122 | void customDraw(const DrawArgs& args) { 123 | int top = 55, bottom = 295; 124 | nvgLineCap(args.vg, NVG_ROUND); 125 | 126 | // Socket scale markers 127 | nvgStrokeColor(args.vg, COLOR_RS_GREY); 128 | nvgStrokeWidth(args.vg, 1); 129 | for(int y = top, cnt = 0; y <= bottom; y += (bottom - top) / 10, cnt++) { 130 | nvgBeginPath(args.vg); 131 | switch(cnt) { 132 | case 0: case 5: case 10: 133 | nvgMoveTo(args.vg, 16, y); 134 | nvgLineTo(args.vg, 58, y); 135 | break; 136 | default: 137 | nvgMoveTo(args.vg, 30, y); 138 | nvgLineTo(args.vg, 44, y); 139 | break; 140 | } 141 | nvgStroke(args.vg); 142 | } 143 | 144 | nvgStrokeColor(args.vg, COLOR_BLACK); 145 | nvgStrokeWidth(args.vg, 5); 146 | 147 | // Left socket slot 148 | nvgBeginPath(args.vg); 149 | nvgMoveTo(args.vg, 22, top); 150 | nvgLineTo(args.vg, 22, bottom); 151 | nvgStroke(args.vg); 152 | 153 | // Right socket slot 154 | nvgBeginPath(args.vg); 155 | nvgMoveTo(args.vg, 52, top); 156 | nvgLineTo(args.vg, 52, bottom); 157 | nvgStroke(args.vg); 158 | } 159 | #include "RSModuleWidgetDraw.hpp" 160 | 161 | void step() override { 162 | if(!module) return; 163 | 164 | if(module->menuaChanged) { 165 | switch(module->vrangea) { 166 | case 0: // 0 - 1 167 | topScaleaLabel->text = "1"; topScaleaLabel->box.pos.x = 3; topScaleaLabel->color = COLOR_GREEN; 168 | midScaleaLabel->text = ".5"; midScaleaLabel->box.pos.x = 3; midScaleaLabel->color = COLOR_GREEN; 169 | botScaleaLabel->text = "0"; botScaleaLabel->box.pos.x = 3; botScaleaLabel->color = COLOR_RS_GREY; 170 | break; 171 | case 1: // 0 - 5 172 | topScaleaLabel->text = "5"; topScaleaLabel->box.pos.x = 3; topScaleaLabel->color = COLOR_GREEN; 173 | midScaleaLabel->text = "2.5"; midScaleaLabel->box.pos.x = 3; midScaleaLabel->color = COLOR_GREEN; 174 | botScaleaLabel->text = "0"; botScaleaLabel->box.pos.x = 3; botScaleaLabel->color = COLOR_RS_GREY; 175 | break; 176 | case 2: // 0 - 10 177 | topScaleaLabel->text = "10"; topScaleaLabel->box.pos.x = 3; topScaleaLabel->color = COLOR_GREEN; 178 | midScaleaLabel->text = "5"; midScaleaLabel->box.pos.x = 3; midScaleaLabel->color = COLOR_GREEN; 179 | botScaleaLabel->text = "0"; botScaleaLabel->box.pos.x = 3; botScaleaLabel->color = COLOR_RS_GREY; 180 | break; 181 | case 3: // -2 - 2 182 | topScaleaLabel->text = "2"; topScaleaLabel->box.pos.x = 3; topScaleaLabel->color = COLOR_GREEN; 183 | midScaleaLabel->text = "0"; midScaleaLabel->box.pos.x = 3; midScaleaLabel->color = COLOR_RS_GREY; 184 | botScaleaLabel->text = "2"; botScaleaLabel->box.pos.x = 3; botScaleaLabel->color = COLOR_RED; 185 | break; 186 | case 4: // -5 - 5 187 | topScaleaLabel->text = "5"; topScaleaLabel->box.pos.x = 3; topScaleaLabel->color = COLOR_GREEN; 188 | midScaleaLabel->text = "0"; midScaleaLabel->box.pos.x = 3; midScaleaLabel->color = COLOR_RS_GREY; 189 | botScaleaLabel->text = "5"; botScaleaLabel->box.pos.x = 3; botScaleaLabel->color = COLOR_RED; 190 | break; 191 | case 5: // -10 - 10 192 | topScaleaLabel->text = "10"; topScaleaLabel->box.pos.x = 3; topScaleaLabel->color = COLOR_GREEN; 193 | midScaleaLabel->text = "0"; midScaleaLabel->box.pos.x = 3; midScaleaLabel->color = COLOR_RS_GREY; 194 | botScaleaLabel->text = "10"; botScaleaLabel->box.pos.x = 3; botScaleaLabel->color = COLOR_RED; 195 | break; 196 | } 197 | module->menuaChanged = false; 198 | } 199 | 200 | if(module->menubChanged) { 201 | switch(module->vrangeb) { 202 | case 0: // 0 - 1 203 | topScalebLabel->text = "1"; topScalebLabel->box.pos.x = 67; topScalebLabel->color = COLOR_GREEN; 204 | midScalebLabel->text = ".5"; midScalebLabel->box.pos.x = 65; midScalebLabel->color = COLOR_GREEN; 205 | botScalebLabel->text = "0"; botScalebLabel->box.pos.x = 67; botScalebLabel->color = COLOR_RS_GREY; 206 | break; 207 | case 1: // 0 - 5 208 | topScalebLabel->text = "5"; topScalebLabel->box.pos.x = 67; topScalebLabel->color = COLOR_GREEN; 209 | midScalebLabel->text = "2.5"; midScalebLabel->box.pos.x = 63; midScalebLabel->color = COLOR_GREEN; 210 | botScalebLabel->text = "0"; botScalebLabel->box.pos.x = 67; botScalebLabel->color = COLOR_RS_GREY; 211 | break; 212 | case 2: // 0 - 10 213 | topScalebLabel->text = "10"; topScalebLabel->box.pos.x = 64; topScalebLabel->color = COLOR_GREEN; 214 | midScalebLabel->text = "5"; midScalebLabel->box.pos.x = 67; midScalebLabel->color = COLOR_GREEN; 215 | botScalebLabel->text = "0"; botScalebLabel->box.pos.x = 67; botScalebLabel->color = COLOR_RS_GREY; 216 | break; 217 | case 3: // -2 - 2 218 | topScalebLabel->text = "2"; topScalebLabel->box.pos.x = 67; topScalebLabel->color = COLOR_GREEN; 219 | midScalebLabel->text = "0"; midScalebLabel->box.pos.x = 67; midScalebLabel->color = COLOR_RS_GREY; 220 | botScalebLabel->text = "2"; botScalebLabel->box.pos.x = 67; botScalebLabel->color = COLOR_RED; 221 | break; 222 | case 4: // -5 - 5 223 | topScalebLabel->text = "5"; topScalebLabel->box.pos.x = 67; topScalebLabel->color = COLOR_GREEN; 224 | midScalebLabel->text = "0"; midScalebLabel->box.pos.x = 67; midScalebLabel->color = COLOR_RS_GREY; 225 | botScalebLabel->text = "5"; botScalebLabel->box.pos.x = 67; botScalebLabel->color = COLOR_RED; 226 | break; 227 | case 5: // -10 - 10 228 | topScalebLabel->text = "10"; topScalebLabel->box.pos.x = 64; topScalebLabel->color = COLOR_GREEN; 229 | midScalebLabel->text = "0"; midScalebLabel->box.pos.x = 67; midScalebLabel->color = COLOR_RS_GREY; 230 | botScalebLabel->text = "10"; botScalebLabel->box.pos.x = 64; botScalebLabel->color = COLOR_RED; 231 | break; 232 | } 233 | module->menubChanged = false; 234 | } 235 | 236 | float inav = RSclamp(module->inputs[RSBoogieBay::INA_INPUT].getVoltage(), -10.f, 10.f); 237 | float inbv = RSclamp(module->inputs[RSBoogieBay::INB_INPUT].getVoltage(), -10.f, 10.f); 238 | int yposa = 0, yposb = 0; 239 | 240 | switch(module->vrangea) { 241 | case 0: // 0 - 1 242 | inav = clamp(inav, 0.f, 1.f); 243 | yposa = mm2px(95 - inav * 80); 244 | break; 245 | case 1: // 0 - 5 246 | inav = clamp(inav, 0.f, 5.f); 247 | yposa = mm2px(95 - inav * 16); 248 | break; 249 | case 2: // 0 - 10 250 | inav = clamp(inav, 0.f, 10.f); 251 | yposa = mm2px(95 - inav * 8); 252 | break; 253 | case 3: // -2 - 2 254 | inav = clamp(inav, -2.f, 2.f); 255 | inav += 2; 256 | yposa = mm2px(95 - inav * 20); 257 | break; 258 | case 4: // -5 - 5 259 | inav = clamp(inav, -5.f, 5.f); 260 | inav += 5; 261 | yposa = mm2px(95 - inav * 8); 262 | break; 263 | case 5: // -10 - 10 264 | inav = clamp(inav, -10.f, 10.f); 265 | inav += 10; 266 | yposa = mm2px(95 - inav * 4); 267 | break; 268 | default: 269 | break; 270 | }; 271 | 272 | switch(module->vrangeb) { 273 | case 0: // 0 - 1 274 | inbv = clamp(inbv, 0.f, 1.f); 275 | yposb = mm2px(95 - inbv * 80); 276 | break; 277 | case 1: // 0 - 5 278 | inbv = clamp(inbv, 0.f, 5.f); 279 | yposb = mm2px(95 - inbv * 16); 280 | break; 281 | case 2: // 0 - 10 282 | inbv = clamp(inbv, 0.f, 10.f); 283 | yposb = mm2px(95 - inbv * 8); 284 | break; 285 | case 3: // -2 - 2 286 | inbv = clamp(inbv, -2.f, 2.f); 287 | inbv += 2; 288 | yposb = mm2px(95 - inbv * 20); 289 | break; 290 | case 4: // -5 - 5 291 | inbv = clamp(inbv, -5.f, 5.f); 292 | inbv += 5; 293 | yposb = mm2px(95 - inbv * 8); 294 | break; 295 | case 5: // -10 - 10 296 | inbv = clamp(inbv, -10.f, 10.f); 297 | inbv += 10; 298 | yposb = mm2px(95 - inbv * 4); 299 | break; 300 | default: 301 | break; 302 | }; 303 | 304 | ina->box.pos.y = yposa; 305 | inb->box.pos.y = yposb; 306 | 307 | ModuleWidget::step(); 308 | } 309 | 310 | 311 | void appendContextMenu(Menu* menu) override { 312 | RSBoogieBay* module = dynamic_cast(this->module); 313 | 314 | std::string rangeNames[6] = {"0V - 1V", "0V - 5V", "0V - 10V", "-2V - 2V", "-5V - 5V", "-10V - 10V"}; 315 | 316 | menu->addChild(new MenuEntry); 317 | 318 | menu->addChild(createMenuLabel("Voltage Range A")); 319 | 320 | struct RangeaItem : MenuItem { 321 | RSBoogieBay* module; 322 | int vrangea; 323 | void onAction(const event::Action& e) override { 324 | module->vrangea = vrangea; 325 | module->menuaChanged = true; 326 | } 327 | }; 328 | 329 | for(int i = 0; i < 6; i++) { 330 | RangeaItem* rangeaItem = createMenuItem(rangeNames[i]); 331 | rangeaItem->rightText = CHECKMARK(module->vrangea == i); 332 | rangeaItem->module = module; 333 | rangeaItem->vrangea = i; 334 | menu->addChild(rangeaItem); 335 | } 336 | 337 | menu->addChild(createMenuLabel("Voltage Range B")); 338 | 339 | struct RangebItem : MenuItem { 340 | RSBoogieBay* module; 341 | int vrangeb; 342 | void onAction(const event::Action& e) override { 343 | module->vrangeb = vrangeb; 344 | module->menubChanged = true; 345 | } 346 | }; 347 | 348 | for(int i = 0; i < 6; i++) { 349 | RangebItem* rangebItem = createMenuItem(rangeNames[i]); 350 | rangebItem->rightText = CHECKMARK(module->vrangeb == i); 351 | rangebItem->module = module; 352 | rangebItem->vrangeb = i; 353 | menu->addChild(rangebItem); 354 | } 355 | } 356 | }; 357 | 358 | 359 | Model *modelRSBoogieBay = createModel("RSBoogieBay"); 360 | -------------------------------------------------------------------------------- /src/RSBoogieBayH8.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSBoogieBayH8 : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | ENUMS(LEFT_SCALE_BUTTONS, 8), 9 | ENUMS(RIGHT_SCALE_BUTTONS, 8), 10 | NUM_PARAMS 11 | }; 12 | enum InputIds { 13 | ENUMS(INPUTS, 8), 14 | NUM_INPUTS 15 | }; 16 | enum OutputIds { 17 | ENUMS(LEFT_OUTPUTS, 8), 18 | ENUMS(RIGHT_OUTPUTS, 8), 19 | POLY_LEFT_OUTPUT, 20 | POLY_RIGHT_OUTPUT, 21 | NUM_OUTPUTS 22 | }; 23 | enum LightIds { 24 | NUM_LIGHTS 25 | }; 26 | 27 | dsp::BooleanTrigger themeTrigger; 28 | 29 | dsp::ClockDivider scaleDivider; 30 | dsp::BooleanTrigger leftScaleTrigger[8]; 31 | dsp::BooleanTrigger rightScaleTrigger[8]; 32 | 33 | RSScribbleStrip *ss[8]; 34 | 35 | bool scaleChanged = true; 36 | RSLabel *leftScaleLabels[8]; // Move to widget 37 | int leftScale[8] = {}; 38 | RSLabel *rightScaleLabels[8]; // Move to widget 39 | int rightScale[8] = {}; 40 | 41 | 42 | RSBoogieBayH8() { 43 | scaleDivider.setDivision(4096); 44 | 45 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 46 | 47 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 48 | 49 | for(int i = 0; i < 8; i++) { 50 | configParam(LEFT_SCALE_BUTTONS + i, 0.f, 1.f, 0.f, "SCALE"); 51 | configParam(RIGHT_SCALE_BUTTONS + i, 0.f, 1.f, 0.f, "SCALE"); 52 | leftScale[i] = 3; rightScale[i] = 2; 53 | } 54 | } 55 | 56 | void process(const ProcessArgs &args) override { 57 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 58 | RSTheme++; 59 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 60 | } 61 | 62 | outputs[POLY_LEFT_OUTPUT].setChannels(8); 63 | outputs[POLY_RIGHT_OUTPUT].setChannels(8); 64 | 65 | if(scaleDivider.process()) { 66 | for(int i = 0; i < 8; i++) { 67 | if(leftScaleTrigger[i].process(params[LEFT_SCALE_BUTTONS + i].getValue())) { 68 | ++leftScale[i]; if(leftScale[i] > 4) leftScale[i] = 0; 69 | scaleChanged = true; 70 | } 71 | if(rightScaleTrigger[i].process(params[RIGHT_SCALE_BUTTONS + i].getValue())) { 72 | ++rightScale[i]; if(rightScale[i] > 3) rightScale[i] = 0; 73 | scaleChanged = true; 74 | } 75 | } 76 | } 77 | 78 | for(int i = 0; i < 8; i++) { 79 | float inv = inputs[INPUTS + i].getVoltage(); 80 | outputs[LEFT_OUTPUTS + i].setVoltage(inv); 81 | outputs[RIGHT_OUTPUTS + i].setVoltage(inv); 82 | outputs[POLY_LEFT_OUTPUT].setVoltage(inv, i); 83 | outputs[POLY_RIGHT_OUTPUT].setVoltage(inv, i); 84 | } 85 | } 86 | 87 | void onReset() override { 88 | for(int i = 0; i < 8; i++) { 89 | leftScale[i] = 3; rightScale[i] = 2; 90 | ss[i]->text = "_"; 91 | } 92 | } 93 | 94 | json_t* dataToJson() override { 95 | json_t* rootJ = json_object(); 96 | 97 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 98 | 99 | char ssn[4], lsn[4], rsn[4]; 100 | 101 | for(int i = 0; i < 8; i++) { 102 | // Scribble strips 103 | json_t* ssj = json_string(ss[i]->text.c_str()); 104 | sprintf(ssn, "SS%i", i); 105 | json_object_set_new(rootJ, ssn, ssj); 106 | 107 | // Left scales 108 | json_t* lsj = json_string(leftScaleLabels[i]->text.c_str()); 109 | sprintf(lsn, "LS%i", i); 110 | json_object_set_new(rootJ, lsn, lsj); 111 | 112 | // Right scales 113 | json_t* rsj = json_string(rightScaleLabels[i]->text.c_str()); 114 | sprintf(rsn, "RS%i", i); 115 | json_object_set_new(rootJ, rsn, rsj); 116 | } 117 | 118 | return rootJ; 119 | } 120 | 121 | void dataFromJson(json_t* rootJ) override { 122 | json_t* themeJ = json_object_get(rootJ, "theme"); 123 | 124 | if(themeJ) RSTheme = json_integer_value(themeJ); 125 | 126 | char ssn[4], lsn[4], rsn[4]; 127 | 128 | for(int i = 0; i < 8; i++) { 129 | // Scribble strips 130 | sprintf(ssn, "SS%i", i); 131 | json_t* ssj = json_object_get(rootJ, ssn); 132 | if(ssj) ss[i]->text = json_string_value(ssj); 133 | 134 | // Left scales 135 | sprintf(lsn, "LS%i", i); 136 | json_t* lsj = json_object_get(rootJ, lsn); 137 | if(lsj) { 138 | int ls = std::stoi(json_string_value(lsj)); 139 | //INFO("Racket Science: leftScale %i %i", i, ls); 140 | 141 | // SIMPLIFY!!! leftScale / rightScale will be -10 to + 10, scaleLabels can be created at run time 142 | 143 | switch(ls) { 144 | case 0: leftScale[i] = 0; leftScaleLabels[i]->text = "0"; break; 145 | case 1: leftScale[i] = 1; leftScaleLabels[i]->text = "1"; break; 146 | case 2: leftScale[i] = 2; leftScaleLabels[i]->text = "2"; break; 147 | case 5: leftScale[i] = 3; leftScaleLabels[i]->text = "5"; break; 148 | case 10: leftScale[i] = 4; leftScaleLabels[i]->text = "10"; break; 149 | } 150 | } 151 | 152 | // Right scales 153 | sprintf(rsn, "RS%i", i); 154 | json_t* rsj = json_object_get(rootJ, rsn); 155 | if(rsj) { 156 | int rs = std::stoi(json_string_value(rsj)); 157 | //INFO("Racket Science: rightScale %i %i", i, rs); 158 | switch(rs) { 159 | case 1: rightScale[i] = 0; rightScaleLabels[i]->text = "1"; break; 160 | case 2: rightScale[i] = 1; rightScaleLabels[i]->text = "2"; break; 161 | case 5: rightScale[i] = 2; rightScaleLabels[i]->text = "5"; break; 162 | case 10: rightScale[i] = 3; rightScaleLabels[i]->text = "10"; break; 163 | } 164 | } 165 | } 166 | } 167 | }; 168 | 169 | 170 | struct RSBoogieBayH8Widget : ModuleWidget { 171 | RSBoogieBayH8* module; 172 | 173 | PortWidget *in[8]; 174 | int middle, left, right; 175 | 176 | RSBoogieBayH8Widget(RSBoogieBayH8 *module) { 177 | INFO("Racket Science: RSBoogieBayH8Widget()"); 178 | 179 | setModule(module); 180 | this->module = module; 181 | 182 | box.size.x = mm2px(5.08 * 25); 183 | middle = box.size.x / 2; 184 | left = 40; 185 | right = box.size.y - 40; 186 | 187 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSBoogieBayH8::THEME_BUTTON)); 188 | 189 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "BOOGIE BAY H8", 14, module)); 190 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12, module)); 191 | 192 | for(int i = 0; i < 8; i++) { 193 | in[i] = createInputCentered(Vec(middle, 40 + (i * 40)), module, RSBoogieBayH8::INPUTS + i); addInput(in[i]); 194 | addOutput(createOutputCentered(Vec(left, 40 + (i * 40)), module, RSBoogieBayH8::LEFT_OUTPUTS + i)); 195 | addOutput(createOutputCentered(Vec(right, 40 + (i * 40)), module, RSBoogieBayH8::RIGHT_OUTPUTS + i)); 196 | addParam(createParamCentered(Vec(left + 20, 40 + (i * 40)), module, RSBoogieBayH8::LEFT_SCALE_BUTTONS + i)); 197 | addParam(createParamCentered(Vec(right - 20, 40 + (i * 40)), module, RSBoogieBayH8::RIGHT_SCALE_BUTTONS + i)); 198 | if(module) { 199 | addChild(module->ss[i] = new RSScribbleStrip(left + 25, 25 + (i * 40))); 200 | addChild(module->leftScaleLabels[i] = new RSLabel(left + 16, 43 + (i * 40), "10", 10, COLOR_RED)); 201 | addChild(module->rightScaleLabels[i] = new RSLabel(right - 24, 43 + (i * 40), "10", 10, COLOR_GREEN)); 202 | } 203 | } 204 | 205 | addOutput(createOutputCentered(Vec(left, 360), module, RSBoogieBayH8::POLY_LEFT_OUTPUT)); 206 | addOutput(createOutputCentered(Vec(right, 360), module, RSBoogieBayH8::POLY_RIGHT_OUTPUT)); 207 | } 208 | 209 | void customDraw(const DrawArgs& args) { 210 | // Socket slots 211 | nvgLineCap(args.vg, NVG_ROUND); 212 | nvgStrokeColor(args.vg, COLOR_BLACK); 213 | nvgStrokeWidth(args.vg, 5); 214 | 215 | for(int i = 0; i < 8; i++) { 216 | nvgBeginPath(args.vg); 217 | nvgMoveTo(args.vg, left + 30, 40 + (i * 40)); 218 | nvgLineTo(args.vg, right - 30, 40 + (i * 40)); 219 | nvgStroke(args.vg); 220 | } 221 | 222 | } 223 | #include "RSModuleWidgetDraw.hpp" 224 | 225 | void step() override { 226 | if(!module) return; 227 | 228 | static float ls = 0, rs = 0; 229 | 230 | for(int i = 0; i < 8; i++) { 231 | module->leftScaleLabels[i]->color = COLOR_RED; 232 | switch(module->leftScale[i]) { 233 | case 0: module->leftScaleLabels[i]->text = "0"; ls = 0; module->leftScaleLabels[i]->color = COLOR_RS_GREY; break; 234 | case 1: module->leftScaleLabels[i]->text = "1"; ls = -1; break; 235 | case 2: module->leftScaleLabels[i]->text = "2"; ls = -2; break; 236 | case 3: module->leftScaleLabels[i]->text = "5"; ls = -5; break; 237 | case 4: module->leftScaleLabels[i]->text = "10"; ls = -10; break; 238 | } 239 | switch(module->rightScale[i]) { 240 | case 0: module->rightScaleLabels[i]->text = "1"; rs = 1; break; 241 | case 1: module->rightScaleLabels[i]->text = "2"; rs = 2; break; 242 | case 2: module->rightScaleLabels[i]->text = "5"; rs = 5; break; 243 | case 3: module->rightScaleLabels[i]->text = "10"; rs = 10; break; 244 | } 245 | float inv = RSclamp(module->inputs[RSBoogieBayH8::INPUTS + i].getVoltage(), ls, rs); 246 | in[i]->box.pos.x = RSscale(inv, ls, rs, 65, 290); 247 | } 248 | 249 | ModuleWidget::step(); 250 | } 251 | }; 252 | 253 | Model *modelRSBoogieBayH8 = createModel("RSBoogieBayH8"); -------------------------------------------------------------------------------- /src/RSCVHeat.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSCVHeat : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | GAIN_KNOB, 9 | LOSS_KNOB, 10 | RESET_BUTTON, 11 | NUM_PARAMS 12 | }; 13 | enum InputIds { 14 | CV_INPUT, 15 | NUM_INPUTS 16 | }; 17 | enum OutputIds { 18 | NUM_OUTPUTS 19 | }; 20 | enum LightIds { 21 | NUM_LIGHTS 22 | }; 23 | 24 | RSCVHeat() { 25 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 26 | 27 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 28 | 29 | configParam(GAIN_KNOB, 0.00001f, 0.001f, 0.0005f, "GAIN"); 30 | configParam(LOSS_KNOB, 0.f, 0.001f, 0.0005f, "LOSS"); 31 | configParam(RESET_BUTTON, 0.f, 1.f, 0.f, "RESET"); 32 | } 33 | 34 | dsp::BooleanTrigger themeTrigger; 35 | dsp::BooleanTrigger resetTrigger; 36 | 37 | #define SAMPLES 96 38 | float heat[SAMPLES] = {}; 39 | float heatGain; 40 | float heatLoss; 41 | 42 | void process(const ProcessArgs &args) override { 43 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 44 | RSTheme++; 45 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 46 | } 47 | 48 | float cvIn = RSclamp(inputs[CV_INPUT].getVoltage(), -10.f, 10.f); 49 | 50 | heatGain = params[GAIN_KNOB].getValue(); 51 | heatLoss = params[LOSS_KNOB].getValue(); 52 | 53 | int heatIdx = (int)RSscale(cvIn, -10.f, +10.f, 0, SAMPLES - 1); 54 | heat[heatIdx] += heatGain; 55 | if(heat[heatIdx] > 1.f) heat[heatIdx] = 1.f; 56 | 57 | for(int i = 0; i < SAMPLES; i++) { 58 | heat[i] -= heatLoss; 59 | if(heat[i] < 0.f) heat[i] = 0.f; 60 | } 61 | 62 | if(resetTrigger.process(params[RESET_BUTTON].getValue())) { 63 | onReset(); 64 | } 65 | } 66 | 67 | void onReset() override { 68 | std::memset(heat, 0, sizeof(heat)); 69 | } 70 | 71 | json_t* dataToJson() override { 72 | json_t* rootJ = json_object(); 73 | 74 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 75 | 76 | return rootJ; 77 | } 78 | 79 | void dataFromJson(json_t* rootJ) override { 80 | json_t* themeJ = json_object_get(rootJ, "theme"); 81 | 82 | if(themeJ) RSTheme = json_integer_value(themeJ); 83 | } 84 | }; 85 | 86 | 87 | struct RSCVHeatDisplay : TransparentWidget { 88 | RSCVHeat* module; 89 | float *buffer; 90 | 91 | RSCVHeatDisplay(RSCVHeat* module, float buffer[], int x, int y, int xs, int ys) { 92 | this->module = module; 93 | this->buffer = buffer; 94 | 95 | box.pos = Vec(x, y); 96 | box.size = Vec(xs, ys); 97 | }; 98 | 99 | void draw(const DrawArgs& args) override { 100 | 101 | // Bounding box 102 | nvgStrokeColor(args.vg, COLOR_RS_BRONZE); 103 | nvgFillColor(args.vg, COLOR_BLACK); 104 | nvgStrokeWidth(args.vg, 1.5f); 105 | 106 | nvgBeginPath(args.vg); 107 | nvgRoundedRect(args.vg, box.pos.x, box.pos.y, box.size.x, box.size.y, 5); 108 | nvgStroke(args.vg); 109 | nvgFill(args.vg); 110 | 111 | if(!module) { 112 | return; 113 | } 114 | 115 | // Rounded edges needed here 116 | for(int i = 2; i < box.size.y - 1; i++) { 117 | nvgStrokeColor(args.vg, nvgHSL(0, 1, buffer[SAMPLES - (int)(SAMPLES / box.size.y * i)])); 118 | nvgBeginPath(args.vg); 119 | nvgMoveTo(args.vg, box.pos.x + 2, box.pos.y + i); 120 | nvgLineTo(args.vg, box.pos.x + box.size.x - 2, box.pos.y + i); 121 | nvgStroke(args.vg); 122 | } 123 | }; 124 | }; 125 | 126 | 127 | struct RSCVHeatWidget : ModuleWidget { 128 | RSCVHeat* module; 129 | 130 | RSCVHeatWidget(RSCVHeat *module) { 131 | INFO("Racket Science: RSCVHeatWidget()"); 132 | 133 | setModule(module); 134 | this->module = module; 135 | 136 | box.size = Vec(RACK_GRID_WIDTH * 6, RACK_GRID_HEIGHT); 137 | int middle = box.size.x / 2 + 1; 138 | 139 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSCVHeat::THEME_BUTTON)); 140 | 141 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "CVHEAT", 14, module)); 142 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12, module)); 143 | 144 | middle = middle + (middle / 2); 145 | 146 | addParam(createParamCentered(Vec(middle, 36), module, RSCVHeat::GAIN_KNOB)); 147 | addChild(new RSLabelCentered(middle, 60, "GAIN", 10, module)); 148 | 149 | addParam(createParamCentered(Vec(middle, 78), module, RSCVHeat::LOSS_KNOB)); 150 | addChild(new RSLabelCentered(middle, 102, "LOSS", 10, module)); 151 | 152 | addParam(createParamCentered(Vec(middle, 118), module, RSCVHeat::RESET_BUTTON)); 153 | addChild(new RSLabelCentered(middle, 121, "RESET", 10, module)); 154 | 155 | // Top label & invisibutton for top scale 156 | 157 | // CV heat display 158 | addChild(new RSCVHeatDisplay(module, module->heat, 3, 10, (box.size.x / 2) - 11, 340)); 159 | 160 | // Bottom label & invisibutton for bottom scale 161 | 162 | addInput(createInputCentered(Vec(middle, 335), module, RSCVHeat::CV_INPUT)); 163 | addChild(new RSLabelCentered(middle, 357, "CV", 10, module)); 164 | } 165 | 166 | void customDraw(const DrawArgs& args) {} 167 | #include "RSModuleWidgetDraw.hpp" 168 | 169 | void step() override { 170 | if(!module) return; 171 | 172 | ModuleWidget::step(); 173 | } 174 | }; 175 | 176 | Model *modelRSCVHeat = createModel("RSCVHeat"); -------------------------------------------------------------------------------- /src/RSGroundControl.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | static bool owned = false; 6 | 7 | struct RSGroundControl : RSModule { 8 | bool running = false; 9 | 10 | enum ParamIds { 11 | BGHUE_KNOB, BGSAT_KNOB, BGLUM_KNOB, 12 | LBHUE_KNOB, LBSAT_KNOB, LBLUM_KNOB, 13 | SSHUE_KNOB, SSSAT_KNOB, SSLUM_KNOB, 14 | LEDA_KNOB, LEDB_KNOB, 15 | THEME_KNOB, 16 | NUM_PARAMS 17 | }; 18 | enum InputIds { 19 | STEALTH_INPUT, 20 | NUM_INPUTS 21 | }; 22 | enum OutputIds { 23 | NUM_OUTPUTS 24 | }; 25 | enum LightIds { 26 | NUM_LIGHTS 27 | }; 28 | 29 | RSGroundControl() { 30 | if(!owned) { 31 | owned = true; 32 | running = true; 33 | } 34 | 35 | RSTheme = 0; 36 | 37 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 38 | 39 | configParam(BGHUE_KNOB, 0.f, 1.f, 0.5f, "HUE"); 40 | configParam(BGSAT_KNOB, 0.f, 1.f, 0.5f, "SAT"); 41 | configParam(BGLUM_KNOB, 0.f, 1.f, 0.5f, "LUM"); 42 | configParam(LBHUE_KNOB, 0.f, 1.f, 0.5f, "HUE"); 43 | configParam(LBSAT_KNOB, 0.f, 1.f, 0.5f, "SAT"); 44 | configParam(LBLUM_KNOB, 0.f, 1.f, 0.5f, "LUM"); 45 | configParam(SSHUE_KNOB, 0.f, 1.f, 0.5f, "HUE"); 46 | configParam(SSSAT_KNOB, 0.f, 1.f, 0.5f, "SAT"); 47 | configParam(SSLUM_KNOB, 0.f, 1.f, 0.5f, "LUM"); 48 | configParam(LEDA_KNOB, 0.f, 1.f, 0.5f, "HUE"); 49 | configParam(LEDB_KNOB, 0.f, 1.f, 0.5f, "HUE"); 50 | 51 | configParam(THEME_KNOB, 0.f, RSGlobal.themeCount - 1.f, 0.0f, "THEME"); 52 | } 53 | 54 | void process(const ProcessArgs &args) override { 55 | } 56 | 57 | void updateParams() { 58 | if(!running) return; 59 | 60 | params[RSGroundControl::BGHUE_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].bghsl.hue); 61 | params[RSGroundControl::BGSAT_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].bghsl.sat); 62 | params[RSGroundControl::BGLUM_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].bghsl.lum); 63 | params[RSGroundControl::LBHUE_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].lbhsl.hue); 64 | params[RSGroundControl::LBSAT_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].lbhsl.sat); 65 | params[RSGroundControl::LBLUM_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].lbhsl.lum); 66 | params[RSGroundControl::SSHUE_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].sshsl.hue); 67 | params[RSGroundControl::SSSAT_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].sshsl.sat); 68 | params[RSGroundControl::SSLUM_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].sshsl.lum); 69 | params[RSGroundControl::LEDA_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].ledAh); 70 | params[RSGroundControl::LEDB_KNOB].setValue(RSGlobal.themes[RSGlobal.themeIdx].ledBh); 71 | } 72 | 73 | void onReset() override { 74 | // How about just removing the RacketScience settings dir & calling SaveRSGlobal? 75 | float hue = 0.f; 76 | float hueStep = 1.f / RSGlobal.themeCount; 77 | // Amend this to 1-13, customise 0, 14 & 15 78 | for(int i = 0; i < RSGlobal.themeCount; i++, hue += hueStep) { 79 | RSGlobal.themes[i].bghsl = {hue, .6f, .5f}; 80 | RSGlobal.themes[i].lbhsl = {hue, .8f, .9f}; 81 | RSGlobal.themes[i].sshsl = {hue, .7f, .8f}; 82 | // LEDs here too once complete 83 | updateRSTheme(i); 84 | } 85 | 86 | RSGlobal.themeIdx = 0; 87 | updateParams(); 88 | saveRSGlobal(); 89 | } 90 | 91 | json_t* dataToJson() override { 92 | json_t* rootJ = json_object(); 93 | 94 | return rootJ; 95 | } 96 | 97 | void dataFromJson(json_t* rootJ) override { 98 | 99 | } 100 | 101 | ~RSGroundControl() { 102 | if(running) { 103 | owned = false; 104 | } 105 | } 106 | }; 107 | 108 | // Move to RSComponents.hpp once perfected 109 | struct RSLedAWidget : TransparentWidget { 110 | NVGcolor bgColor = nvgRGBA(0, 0, 0, 0); 111 | NVGcolor color = nvgRGBA(0, 0, 0, 0); 112 | NVGcolor borderColor = nvgRGBA(0, 0, 0, 0); 113 | 114 | RSLedAWidget(int x, int y, int size = 15) { 115 | box.pos = Vec(x, y); 116 | box.size = Vec(size, size); 117 | } 118 | 119 | void draw(const DrawArgs& args) override { 120 | drawLed(args); 121 | drawHalo(args); 122 | } 123 | 124 | void drawLed(const DrawArgs& args) { 125 | nvgStrokeColor(args.vg, COLOR_RS_BRONZE); 126 | color = RSGlobal.themes[RSGlobal.themeIdx].lAColor; 127 | nvgFillColor(args.vg, color); 128 | nvgBeginPath(args.vg); 129 | // nvgRoundedRect(args.vg, box.pos.x, box.pos.y, box.size.x, box.size.y, 10); 130 | nvgRoundedRect(args.vg, 0, 0, box.size.x, box.size.y, 10); 131 | nvgStroke(args.vg); 132 | nvgFill(args.vg); 133 | } 134 | 135 | void drawHalo(const DrawArgs& args) { 136 | float radius = box.size.x / 2; 137 | float oradius = radius * 4.f; 138 | 139 | nvgBeginPath(args.vg); 140 | nvgRect(args.vg, box.pos.x, box.pos.y, box.size.x, box.size.y); 141 | 142 | NVGpaint paint; 143 | NVGcolor icol = color::mult(color, 0.7f); 144 | NVGcolor ocol = nvgRGB(0, 0, 0); 145 | paint = nvgRadialGradient(args.vg, radius, radius, radius, oradius, icol, ocol); 146 | nvgFillPaint(args.vg, paint); 147 | nvgGlobalCompositeOperation(args.vg, NVG_LIGHTER); 148 | nvgFill(args.vg); 149 | } 150 | }; 151 | 152 | struct RSLedBWidget : TransparentWidget { 153 | NVGcolor bgColor = nvgRGBA(0, 0, 0, 0); 154 | NVGcolor color = nvgRGBA(0, 0, 0, 0); 155 | NVGcolor borderColor = nvgRGBA(0, 0, 0, 0); 156 | 157 | RSLedBWidget(int x, int y, int size = 15) { 158 | box.pos = Vec(x, y); 159 | box.size = Vec(size, size); 160 | } 161 | 162 | void draw(const DrawArgs& args) override { 163 | drawLed(args); 164 | drawHalo(args); 165 | } 166 | 167 | void drawLed(const DrawArgs& args) { 168 | nvgStrokeColor(args.vg, COLOR_RS_BRONZE); 169 | color = RSGlobal.themes[RSGlobal.themeIdx].lBColor; 170 | nvgFillColor(args.vg, color); 171 | nvgBeginPath(args.vg); 172 | // nvgRoundedRect(args.vg, box.pos.x, box.pos.y, box.size.x, box.size.y, 10); 173 | nvgRoundedRect(args.vg, 0, 0, box.size.x, box.size.y, 10); 174 | nvgStroke(args.vg); 175 | nvgFill(args.vg); 176 | } 177 | 178 | void drawHalo(const DrawArgs& args) { 179 | float radius = box.size.x / 2; 180 | float oradius = radius * 4.f; 181 | 182 | nvgBeginPath(args.vg); 183 | nvgRect(args.vg, box.pos.x, box.pos.y, box.size.x, box.size.y); 184 | 185 | NVGpaint paint; 186 | NVGcolor icol = color::mult(color, 0.7f); 187 | NVGcolor ocol = nvgRGB(0, 0, 0); 188 | paint = nvgRadialGradient(args.vg, radius, radius, radius, oradius, icol, ocol); 189 | nvgFillPaint(args.vg, paint); 190 | nvgGlobalCompositeOperation(args.vg, NVG_LIGHTER); 191 | nvgFill(args.vg); 192 | } 193 | }; 194 | 195 | struct RSGroundControlWidget : ModuleWidget { 196 | RSGroundControl* module; 197 | 198 | RSGroundControlWidget(RSGroundControl *module) { 199 | INFO("Racket Science: RSGroundControlWidget()"); 200 | 201 | setModule(module); 202 | this->module = module; 203 | 204 | box.size = Vec(RACK_GRID_WIDTH * 10, RACK_GRID_HEIGHT); 205 | int middle = box.size.x / 2 + 1; 206 | int quarter = middle / 2; 207 | 208 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "GROUND CONTROL", 14)); 209 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12)); 210 | if(module) 211 | if(!module->running) { 212 | addChild(new RSLabelCentered(middle, box.size.y / 2, "DISABLED", 16)); 213 | addChild(new RSLabelCentered(middle, box.size.y / 2 + 12, "ONLY ONE INSTANCE OF GC REQUIRED")); 214 | return; 215 | } 216 | 217 | addChild(new RSLabelCentered(middle, 80, "THIS SPACE AVAILABLE", 16)); 218 | addChild(new RSLabelCentered(middle, 100, "FOR SHORT TERM RENT", 16)); 219 | 220 | /* What else to include? 221 | Current date & time 222 | Elapsed time since start 223 | General purpose CV controllable timer 224 | Countdown alarm 225 | General alarm 226 | 227 | Have a panel behind each set of HSL knobs to show color selected 228 | 229 | */ 230 | 231 | { 232 | int top = 190, left = 40; 233 | int xsp = 30, ysp = 30, los = 30; // x spacing, y spacing, label offset 234 | 235 | 236 | addChild(new RSLabelCentered(middle, top, "ANY COLOUR YOU LIKE", 12)); 237 | 238 | addChild(new RSLabelCentered(left + (xsp * 1), top + (ysp / 2), "HUE")); 239 | addChild(new RSLabelCentered(left + (xsp * 2), top + (ysp / 2), "SAT")); 240 | addChild(new RSLabelCentered(left + (xsp * 3), top + (ysp / 2), "LUM")); 241 | 242 | addChild(new RSLabelCentered(left - 10, top + (ysp * 1) + 3, "BACKGROUND")); 243 | addChild(new RSLabelCentered(left - 10, top + (ysp * 2) + 3, "LABELS")); 244 | addChild(new RSLabelCentered(left - 10, top + (ysp * 3) + 3, "SCRIBBLES")); 245 | addChild(new RSLabelCentered(left - 20, top + (ysp * 4) + 3, "LEDS A")); 246 | addChild(new RSLabelCentered(left - 20, top + (ysp * 5) + 3, "LEDS B")); 247 | 248 | addParam(createParamCentered(Vec(left + (xsp * 1), top + (ysp * 1)), module, RSGroundControl::BGHUE_KNOB)); 249 | addParam(createParamCentered(Vec(left + (xsp * 2), top + (ysp * 1)), module, RSGroundControl::BGSAT_KNOB)); 250 | addParam(createParamCentered(Vec(left + (xsp * 3), top + (ysp * 1)), module, RSGroundControl::BGLUM_KNOB)); 251 | 252 | addParam(createParamCentered(Vec(left + (xsp * 1), top + (ysp * 2)), module, RSGroundControl::LBHUE_KNOB)); 253 | addParam(createParamCentered(Vec(left + (xsp * 2), top + (ysp * 2)), module, RSGroundControl::LBSAT_KNOB)); 254 | addParam(createParamCentered(Vec(left + (xsp * 3), top + (ysp * 2)), module, RSGroundControl::LBLUM_KNOB)); 255 | 256 | addParam(createParamCentered(Vec(left + (xsp * 1), top + (ysp * 3)), module, RSGroundControl::SSHUE_KNOB)); 257 | addParam(createParamCentered(Vec(left + (xsp * 2), top + (ysp * 3)), module, RSGroundControl::SSSAT_KNOB)); 258 | addParam(createParamCentered(Vec(left + (xsp * 3), top + (ysp * 3)), module, RSGroundControl::SSLUM_KNOB)); 259 | 260 | addParam(createParamCentered(Vec(left + (xsp * 1), top + (ysp * 4)), module, RSGroundControl::LEDA_KNOB)); 261 | addParam(createParamCentered(Vec(left + (xsp * 1), top + (ysp * 5)), module, RSGroundControl::LEDB_KNOB)); 262 | 263 | addParam(createParamCentered(Vec(left + (xsp * 2.5), top + (ysp * 4.5)), module, RSGroundControl::THEME_KNOB)); 264 | addChild(new RSLabelCentered(left + (xsp * 2.5), top + (ysp * 5.8), "THEME")); 265 | 266 | addChild(new RSLedAWidget(left - 3, top + (ysp * 4) - 7)); 267 | addChild(new RSLedBWidget(left - 3, top + (ysp * 5) - 7)); 268 | 269 | } 270 | 271 | if(!module) return; 272 | 273 | module->params[RSGroundControl::THEME_KNOB].setValue(RSGlobal.themeIdx); 274 | 275 | module->updateParams(); 276 | } 277 | 278 | void customDraw(const DrawArgs& args) {} 279 | #include "RSModuleWidgetDraw.hpp" 280 | 281 | void step() override { 282 | if(!module) return; 283 | if(!module->running) return; 284 | 285 | static struct rsglobal lastRSGlobal; 286 | 287 | static int lastTheme = 0; 288 | int theme = (int)module->params[RSGroundControl::THEME_KNOB].getValue(); 289 | 290 | if(theme != lastTheme) { 291 | RSGlobal.themeIdx = lastTheme = theme; 292 | module->updateParams(); 293 | updateRSTheme(theme); 294 | saveRSGlobal(); 295 | } 296 | 297 | // Ideally only want to do the following if any params have changed 298 | // How can we achieve that? Can we test if the mouse is over our module, that would help 299 | // No point checking each param for change before reflecting that in RSGlobal, may as well just assign blindly 300 | 301 | RSGlobal.themes[RSGlobal.themeIdx].bghsl.hue = module->params[RSGroundControl::BGHUE_KNOB].getValue(); 302 | RSGlobal.themes[RSGlobal.themeIdx].bghsl.sat = module->params[RSGroundControl::BGSAT_KNOB].getValue(); 303 | RSGlobal.themes[RSGlobal.themeIdx].bghsl.lum = module->params[RSGroundControl::BGLUM_KNOB].getValue(); 304 | 305 | RSGlobal.themes[RSGlobal.themeIdx].lbhsl.hue = module->params[RSGroundControl::LBHUE_KNOB].getValue(); 306 | RSGlobal.themes[RSGlobal.themeIdx].lbhsl.sat = module->params[RSGroundControl::LBSAT_KNOB].getValue(); 307 | RSGlobal.themes[RSGlobal.themeIdx].lbhsl.lum = module->params[RSGroundControl::LBLUM_KNOB].getValue(); 308 | 309 | RSGlobal.themes[RSGlobal.themeIdx].sshsl.hue = module->params[RSGroundControl::SSHUE_KNOB].getValue(); 310 | RSGlobal.themes[RSGlobal.themeIdx].sshsl.sat = module->params[RSGroundControl::SSSAT_KNOB].getValue(); 311 | RSGlobal.themes[RSGlobal.themeIdx].sshsl.lum = module->params[RSGroundControl::SSLUM_KNOB].getValue(); 312 | 313 | RSGlobal.themes[RSGlobal.themeIdx].ledAh = module->params[RSGroundControl::LEDA_KNOB].getValue(); 314 | RSGlobal.themes[RSGlobal.themeIdx].ledBh = module->params[RSGroundControl::LEDB_KNOB].getValue(); 315 | 316 | updateRSTheme(RSGlobal.themeIdx); 317 | 318 | // memcmp should suffice, nothing unusual in rsglobal struct 319 | if(memcmp(&RSGlobal, &lastRSGlobal, sizeof(rsglobal)) != 0) { // Theme settings have changed 320 | lastRSGlobal = RSGlobal; // memcpy? 321 | saveRSGlobal(); 322 | } 323 | 324 | ModuleWidget::step(); 325 | } 326 | }; 327 | 328 | Model *modelRSGroundControl = createModel("RSGroundControl"); 329 | 330 | -------------------------------------------------------------------------------- /src/RSHeat.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSHeat : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | RESET_BUTTON, 9 | GAIN_KNOB, 10 | LOSS_KNOB, 11 | NUM_PARAMS 12 | }; 13 | enum InputIds { 14 | CV_INPUT, 15 | GATE_INPUT, 16 | NUM_INPUTS 17 | }; 18 | enum OutputIds { 19 | NUM_OUTPUTS 20 | }; 21 | enum LightIds { 22 | ENUMS(SEMITONE_LIGHTS, 12), 23 | ENUMS(OCTAVE_LIGHTS, 10), 24 | NUM_LIGHTS 25 | }; 26 | 27 | dsp::BooleanTrigger themeTrigger; 28 | 29 | dsp::SchmittTrigger gateTrigger; 30 | dsp::BooleanTrigger resetTrigger; 31 | 32 | float semiHeat[12] = {}; 33 | float octHeat[10] = {}; 34 | float heatGain = 0.1f; 35 | float heatLoss = 0.1f; 36 | 37 | RSHeat() { 38 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 39 | 40 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 41 | 42 | configParam(GAIN_KNOB, 0.01f, 5.0f, 0.05f, "GAIN"); 43 | configParam(LOSS_KNOB, 0.01f, 0.5f, 0.05f, "LOSS"); 44 | } 45 | 46 | void process(const ProcessArgs &args) override { 47 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 48 | RSTheme++; 49 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 50 | } 51 | 52 | float vOctIn = RSclamp(inputs[CV_INPUT].getVoltage(), -10.f, 10.f); 53 | int noteIdx = note(vOctIn); 54 | int octIdx = clamp(octave(vOctIn) + 4, 0, 9); 55 | 56 | heatGain = params[GAIN_KNOB].getValue(); 57 | heatLoss = params[LOSS_KNOB].getValue(); 58 | 59 | if(gateTrigger.process(inputs[GATE_INPUT].getVoltage())) { 60 | if(semiHeat[noteIdx] < 10.f) semiHeat[noteIdx] += heatGain; 61 | if(semiHeat[noteIdx] > 10.f) semiHeat[noteIdx] = 10.f; 62 | if(octHeat[octIdx] + heatGain < 10.f) octHeat[octIdx] += heatGain; 63 | if(octHeat[octIdx] > 10.f) octHeat[octIdx] = 10.f; 64 | } 65 | 66 | if(resetTrigger.process(params[RESET_BUTTON].getValue())) { 67 | for(int i = 0; i < 12; i++) semiHeat[i] = 0.f; 68 | for(int i = 0; i < 10; i++) octHeat[i] = 0.f; 69 | } 70 | } 71 | 72 | void onReset() override { 73 | std::memset(semiHeat, 0, sizeof(semiHeat)); 74 | std::memset(octHeat, 0, sizeof(octHeat)); 75 | } 76 | 77 | json_t* dataToJson() override { 78 | json_t* rootJ = json_object(); 79 | 80 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 81 | 82 | return rootJ; 83 | } 84 | 85 | void dataFromJson(json_t* rootJ) override { 86 | json_t* themeJ = json_object_get(rootJ, "theme"); 87 | 88 | if(themeJ) RSTheme = json_integer_value(themeJ); 89 | } 90 | }; 91 | 92 | struct RSHeatWidget : ModuleWidget { 93 | RSHeat* module; 94 | Widget* panelBorder; 95 | 96 | RSHeatWidget(RSHeat *module) { 97 | INFO("Racket Science: RSHeatWidget()"); 98 | 99 | setModule(module); 100 | this->module = module; 101 | 102 | panelBorder = new PanelBorder; 103 | addChild(panelBorder); 104 | 105 | box.size.x = mm2px(5.08 * 5); 106 | int middle = box.size.x / 2 + 1; 107 | int third = box.size.x / 3; 108 | 109 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSHeat::THEME_BUTTON)); 110 | 111 | addChild(new RSLabelCentered(middle, box.pos.y + 14, "HEAT", 15, module)); 112 | //addChild(new RSLabelCentered(middle, box.pos.y + 30, "Module Subtitle", 14)); 113 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12, module)); // >= 4HP 114 | //addChild(new RSLabelCentered(middle, box.size.y - 15, "Racket", 12)); 115 | //addChild(new RSLabelCentered(middle, box.size.y - 4, "Science", 12)); 116 | 117 | addInput(createInputCentered(Vec(middle / 2, 30), module, RSHeat::CV_INPUT)); 118 | addChild(new RSLabelCentered(middle / 2, 52, "V/OCT", 10, module)); 119 | 120 | addInput(createInputCentered(Vec(middle + middle / 2, 30), module, RSHeat::GATE_INPUT)); 121 | addChild(new RSLabelCentered(middle + middle / 2, 52, "GATE", 10, module)); 122 | 123 | addParam(createParamCentered(Vec(middle, 68), module, RSHeat::RESET_BUTTON)); 124 | addChild(new RSLabelCentered(middle, 71, "RESET", 10, module)); 125 | 126 | addParam(createParamCentered(Vec(middle / 2, 108), module, RSHeat::GAIN_KNOB)); 127 | addChild(new RSLabelCentered(middle / 2, 132, "GAIN", 10, module)); 128 | 129 | addParam(createParamCentered(Vec(middle + middle / 2, 108), module, RSHeat::LOSS_KNOB)); 130 | addChild(new RSLabelCentered(middle + middle / 2, 132, "LOSS", 10, module)); 131 | 132 | LightWidget *lightWidget; 133 | 134 | // Semitone lights 135 | for(int i = 0 ; i < 12; i++) { 136 | int offset; 137 | switch(i) { 138 | case 1: case 3: case 5: case 8: case 10: offset = 7; break; 139 | default: offset = -7; 140 | } 141 | lightWidget = createLightCentered>(Vec(third - offset, 144 + (i * 19)), module, RSHeat::SEMITONE_LIGHTS + i); 142 | lightWidget->bgColor = nvgRGBA(10, 10, 10, 128); 143 | addChild(lightWidget); 144 | } 145 | 146 | // Octave lights 147 | for(int i = 0; i < 10; i++) { 148 | lightWidget = createLightCentered>(Vec(third * 2 + 7, 144 + (i * 23.25)), module, RSHeat::OCTAVE_LIGHTS + i); 149 | lightWidget->bgColor = nvgRGBA(10, 10, 10, 128); 150 | addChild(lightWidget); 151 | } 152 | } 153 | 154 | void step() override { 155 | if(!module) return; 156 | 157 | for(int i = 0; i < 12; i++) { 158 | module->lights[11 - i].setBrightness(module->semiHeat[i] / 10); 159 | } 160 | for(int i = 0; i < 10; i++) { 161 | module->lights[21 - i].setBrightness(module->octHeat[i] / 10); 162 | } 163 | 164 | for(int i = 0; i < 12; i++) { 165 | if(module->semiHeat[i] > 0.f) module->semiHeat[i] -= module->heatLoss; 166 | if(module->semiHeat[i] < 0.f) module->semiHeat[i] = 0.f; 167 | } 168 | for(int i = 0; i < 10; i++) { 169 | if(module->octHeat[i] > 0.f) module->octHeat[i] -= module->heatLoss; 170 | if(module->octHeat[i] < 0.f) module->octHeat[i] = 0.f; 171 | } 172 | 173 | ModuleWidget::step(); 174 | } 175 | 176 | void customDraw(const DrawArgs& args) {} 177 | #include "RSModuleWidgetDraw.hpp" 178 | }; 179 | 180 | Model *modelRSHeat = createModel("RSHeat"); -------------------------------------------------------------------------------- /src/RSLaunchControl.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSLaunchControl : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | ARM_PARAM, 9 | STEPS_PARAM, 10 | NUM_PARAMS 11 | }; 12 | enum InputIds { 13 | PHASE_IN, 14 | ARM_IN, 15 | NUM_INPUTS 16 | }; 17 | enum OutputIds { 18 | PHASE_OUT, 19 | RUNNING_OUT, 20 | STEP_OUT, 21 | EOC_OUT, 22 | NUM_OUTPUTS 23 | }; 24 | enum LightIds { 25 | STOPPED_LIGHT, // Red 26 | ARMED_LIGHT, // Yellow 27 | RUNNING_LIGHT, // Green 28 | NUM_LIGHTS 29 | }; 30 | 31 | dsp::BooleanTrigger themeTrigger; 32 | 33 | dsp::BooleanTrigger armInTrigger, armTrigger; 34 | 35 | dsp::SchmittTrigger eocTrigger; 36 | 37 | dsp::PulseGenerator stepPulse; 38 | dsp::PulseGenerator eocPulse; 39 | 40 | bool armed = false; 41 | bool running = false; 42 | bool step = false; 43 | bool eoc = false; 44 | 45 | float phaseIn, priorPhaseIn; 46 | 47 | RSLaunchControl() { 48 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 49 | 50 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 51 | 52 | configParam(ARM_PARAM, 0.f, 1.f, 0.f, "ARM"); 53 | configParam(STEPS_PARAM, 2.f, 64.f, 8.f, "STEPS"); 54 | } 55 | 56 | void process(const ProcessArgs &args) override { 57 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 58 | RSTheme++; 59 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 60 | } 61 | 62 | if(inputs[ARM_IN].isConnected()) { 63 | if(armInTrigger.process(inputs[ARM_IN].getVoltage())) { 64 | //if(!running) { 65 | INFO("Racket Science: Launch Control armed"); 66 | armed = true; 67 | //} 68 | } 69 | } 70 | 71 | if(armTrigger.process(params[ARM_PARAM].getValue())) { 72 | if(!running) { 73 | INFO("Racket Science: Launch Control armed via button"); 74 | armed = true; 75 | } 76 | } 77 | 78 | phaseIn = inputs[PHASE_IN].getVoltage(); 79 | 80 | if(armed) { 81 | if(phaseIn < priorPhaseIn) { 82 | INFO("Racket Science: Launch Control running"); 83 | running = true; 84 | armed = false; 85 | priorPhaseIn = phaseIn; // So we don't stop immediately below 86 | params[ARM_PARAM].setValue(0.f); 87 | } 88 | } 89 | 90 | if(running) { 91 | outputs[PHASE_OUT].setVoltage(phaseIn); 92 | if(phaseIn < priorPhaseIn) { 93 | INFO("Racket Science: Launch Control stopped"); 94 | running = false; 95 | eocPulse.trigger(); 96 | stepPulse.trigger(); 97 | } 98 | } 99 | 100 | float steps = params[STEPS_PARAM].getValue(); 101 | float phaseStep = 10.f / steps; 102 | if(running) { 103 | for(float step = phaseStep; step < 10.f; step += phaseStep) { 104 | if(phaseIn > step && priorPhaseIn <= step) { 105 | INFO("Racket Science: step %f", phaseIn); 106 | stepPulse.trigger(); 107 | } 108 | } 109 | } 110 | 111 | step = stepPulse.process(1.f / args.sampleRate); 112 | eoc = eocPulse.process(1.f / args.sampleRate); 113 | 114 | outputs[EOC_OUT].setVoltage(eoc ? 10.f : 0.f); 115 | outputs[RUNNING_OUT].setVoltage(running ? 10.f : 0.f); 116 | outputs[STEP_OUT].setVoltage(step ? 10.f : 0.f); 117 | 118 | //params[ARM_PARAM].setValue(armed ? 1.f : 0.f); 119 | lights[STOPPED_LIGHT].setSmoothBrightness(!running ? 1.f : 0.f, 1.f); 120 | lights[ARMED_LIGHT].setSmoothBrightness(armed ? 1.f : 0.f, 1.f); 121 | lights[RUNNING_LIGHT].setSmoothBrightness(running ? 1.f : 0.f, 1.f); 122 | 123 | priorPhaseIn = phaseIn; 124 | } 125 | 126 | json_t* dataToJson() override { 127 | json_t* rootJ = json_object(); 128 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 129 | 130 | return rootJ; 131 | } 132 | 133 | void dataFromJson(json_t* rootJ) override { 134 | json_t* themeJ = json_object_get(rootJ, "theme"); 135 | if(themeJ) RSTheme = json_integer_value(themeJ); 136 | } 137 | }; 138 | 139 | 140 | struct RSLaunchControlWidget : ModuleWidget { 141 | RSLaunchControl* module; 142 | 143 | RSLaunchControlWidget(RSLaunchControl *module) { 144 | INFO("Racket Science: RSLaunchControlWidget()"); 145 | 146 | setModule(module); 147 | this->module = module; 148 | 149 | box.size = Vec(RACK_GRID_WIDTH * 26, RACK_GRID_HEIGHT); 150 | int middle = box.size.x / 2 + 1; 151 | 152 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSLaunchControl::THEME_BUTTON)); 153 | 154 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "LAUNCH CONTROL", 14, module)); 155 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12, module)); 156 | 157 | int x, y; 158 | 159 | LightWidget *lightWidget; 160 | 161 | // PHASE IN 162 | x = 25; y = 50; 163 | addInput(createInputCentered(Vec(x, y), module, RSLaunchControl::PHASE_IN)); 164 | addChild(new RSLabelCentered(x, y - 18, "PHASE", 10, module)); 165 | 166 | // ARM IN 167 | x += 35; 168 | addInput(createInputCentered(Vec(x, y), module, RSLaunchControl::ARM_IN)); 169 | addChild(new RSLabelCentered(x, y - 18, "ARM", 10, module)); 170 | 171 | // ARM BUTTON 172 | x += 35; 173 | addParam(createParamCentered(Vec(x, y), module, RSLaunchControl::ARM_PARAM)); 174 | addChild(new RSLabelCentered(x, y + 3, "ARM", 10, module)); 175 | 176 | // STOPPED LIGHT 177 | x += 35; 178 | addChild(createLightCentered>(Vec(x, y), module, RSLaunchControl::STOPPED_LIGHT)); 179 | addChild(new RSLabelCentered(x, y - 18, "STOPPED", 10, module)); 180 | 181 | // ARMED LIGHT 182 | x += 35; 183 | addChild(createLightCentered>(Vec(x, y), module, RSLaunchControl::ARMED_LIGHT)); 184 | addChild(new RSLabelCentered(x, y - 18, "ARMED", 10, module)); 185 | 186 | // RUNNING LIGHT 187 | x += 35; 188 | addChild(createLightCentered>(Vec(x, y), module, RSLaunchControl::RUNNING_LIGHT)); 189 | addChild(new RSLabelCentered(x, y - 18, "RUNNING", 10, module)); 190 | 191 | // RUNNING OUT 192 | x += 35; 193 | addOutput(createOutputCentered(Vec(x, y), module, RSLaunchControl::RUNNING_OUT)); 194 | addChild(new RSLabelCentered(x, y - 18, "RUNNING", 10, module)); 195 | 196 | // PHASE OUT 197 | x += 35; 198 | addOutput(createOutputCentered(Vec(x, y), module, RSLaunchControl::PHASE_OUT)); 199 | addChild(new RSLabelCentered(x, y - 18, "PHASE", 10, module)); 200 | 201 | // STEPS 202 | x += 35; 203 | addParam(createParamCentered(Vec(x, y), module, RSLaunchControl::STEPS_PARAM)); 204 | addChild(new RSLabelCentered(x, y - 18, "STEPS", 10, module)); 205 | 206 | // STEP OUT 207 | x += 35; 208 | addOutput(createOutputCentered(Vec(x, y), module, RSLaunchControl::STEP_OUT)); 209 | addChild(new RSLabelCentered(x, y - 18, "STEP", 10, module)); 210 | 211 | // EOC OUT 212 | x += 35; 213 | addOutput(createOutputCentered(Vec(x, y), module, RSLaunchControl::EOC_OUT)); 214 | addChild(new RSLabelCentered(x, y - 18, "EOC", 10, module)); 215 | 216 | } 217 | 218 | void step() override { 219 | if(!module) return; 220 | 221 | ModuleWidget::step(); 222 | } 223 | 224 | void customDraw(const DrawArgs& args) {} 225 | #include "RSModuleWidgetDraw.hpp" 226 | }; 227 | 228 | Model *modelRSLaunchControl = createModel("RSLaunchControl"); -------------------------------------------------------------------------------- /src/RSMFH.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSMFH : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | VOLTAGE_KNOB, 9 | NUM_PARAMS 10 | }; 11 | enum InputIds { 12 | TRIG_IN, 13 | NUM_INPUTS 14 | }; 15 | enum OutputIds { 16 | MINF_OUT, 17 | PINF_OUT, 18 | NAN_OUT, 19 | VOLTAGE_OUT, 20 | EVIL_OUT, 21 | NUM_OUTPUTS 22 | }; 23 | enum LightIds { 24 | NUM_LIGHTS 25 | }; 26 | 27 | dsp::BooleanTrigger themeTrigger; 28 | 29 | RSMFH() { 30 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 31 | 32 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 33 | 34 | configParam(VOLTAGE_KNOB, -20.f, +20.f, 0.f, "VOTLAGE"); 35 | } 36 | 37 | void process(const ProcessArgs &args) override { 38 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 39 | RSTheme++; 40 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 41 | } 42 | 43 | outputs[MINF_OUT].setChannels(16); 44 | outputs[PINF_OUT].setChannels(16); 45 | outputs[NAN_OUT].setChannels(16); 46 | outputs[VOLTAGE_OUT].setChannels(16); 47 | outputs[EVIL_OUT].setChannels(16); 48 | 49 | for(int c = 0; c < 16; c++) { 50 | outputs[MINF_OUT].setVoltage(-INFINITY, c); 51 | outputs[PINF_OUT].setVoltage(INFINITY, c); 52 | outputs[NAN_OUT].setVoltage(NAN, c); 53 | outputs[VOLTAGE_OUT].setVoltage(params[VOLTAGE_KNOB].getValue(), c); 54 | 55 | switch(rand() % 6) { 56 | case 0: outputs[EVIL_OUT].setVoltage(-INFINITY, c); break; 57 | case 1: outputs[EVIL_OUT].setVoltage(INFINITY, c); break; 58 | case 2: outputs[EVIL_OUT].setVoltage(-666.666f, c); break; 59 | case 3: outputs[EVIL_OUT].setVoltage(666.666f, c); break; 60 | case 4: outputs[EVIL_OUT].setVoltage(NAN, c); break; 61 | default: outputs[EVIL_OUT].setVoltage(rand(), c); 62 | } 63 | } 64 | } 65 | 66 | json_t* dataToJson() override { 67 | json_t* rootJ = json_object(); 68 | 69 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 70 | 71 | return rootJ; 72 | } 73 | 74 | void dataFromJson(json_t* rootJ) override { 75 | json_t* themeJ = json_object_get(rootJ, "theme"); 76 | 77 | if(themeJ) RSTheme = json_integer_value(themeJ); 78 | } 79 | }; 80 | 81 | struct RSMFHWidget : ModuleWidget { 82 | RSMFH* module; 83 | 84 | RSMFHWidget(RSMFH *module) { 85 | INFO("Racket Science: RSMFHWidget()"); 86 | 87 | setModule(module); 88 | this->module = module; 89 | 90 | box.size = Vec(RACK_GRID_WIDTH * 3, RACK_GRID_HEIGHT); 91 | int middle = box.size.x / 2 + 1; 92 | 93 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSMFH::THEME_BUTTON)); 94 | 95 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "MODULE", 14, module)); 96 | addChild(new RSLabelCentered(middle, box.pos.y + 25, "FROM", 14, module)); 97 | addChild(new RSLabelCentered(middle, box.pos.y + 37, "HELL", 14, module)); 98 | 99 | addChild(new RSLabelCentered(middle, box.size.y - 15, "Racket", 12, module)); 100 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Science", 12, module)); 101 | 102 | addOutput(createOutputCentered(Vec(23, 72), module, RSMFH::MINF_OUT)); 103 | addChild(new RSLabelCentered(middle, 94, "-INF", 10, module)); 104 | 105 | addOutput(createOutputCentered(Vec(23, 112), module, RSMFH::PINF_OUT)); 106 | addChild(new RSLabelCentered(middle, 134, "+INF", 10, module)); 107 | 108 | addOutput(createOutputCentered(Vec(23, 152), module, RSMFH::NAN_OUT)); 109 | addChild(new RSLabelCentered(middle, 174, "NAN", 10, module)); 110 | 111 | addOutput(createOutputCentered(Vec(23, 218), module, RSMFH::VOLTAGE_OUT)); 112 | addParam(createParamCentered(Vec(23, 248), module, RSMFH::VOLTAGE_KNOB)); 113 | addChild(new RSLabelCentered(middle, 270, "-20 +20", 10, module)); 114 | 115 | addChild(new RSLabel(middle - 15, 306, "!EVIL!", 16, COLOR_RED)); 116 | addOutput(createOutputCentered(Vec(23, 322), module, RSMFH::EVIL_OUT)); 117 | addChild(new RSLabel(middle - 15, 348, "!EVIL!", 16, COLOR_RED)); 118 | } 119 | 120 | void customDraw(const DrawArgs& args) {} 121 | #include "RSModuleWidgetDraw.hpp" 122 | 123 | void step() override { 124 | if(!module) return; 125 | 126 | ModuleWidget::step(); 127 | } 128 | }; 129 | 130 | Model *modelRSMFH = createModel("RSMFH"); -------------------------------------------------------------------------------- /src/RSMajorTom.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSMajorTom : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | 9 | ATT_A_SCALE, 10 | ATT_A_OFFSET, 11 | ATT_B_SCALE, 12 | ATT_B_OFFSET, 13 | 14 | PULSE_A_BUTTON, 15 | GATE_A_BUTTON, 16 | DOOR_A_BUTTON, 17 | 18 | PULSE_B_BUTTON, 19 | GATE_B_BUTTON, 20 | DOOR_B_BUTTON, 21 | 22 | NUM_PARAMS 23 | }; 24 | enum InputIds { 25 | ATT_A_IN, 26 | ATT_B_IN, 27 | 28 | NUM_INPUTS 29 | }; 30 | enum OutputIds { 31 | ATT_A_OUT, 32 | ATT_B_OUT, 33 | 34 | PULSEGATE_A_OUT, 35 | PULSEGATE_B_OUT, 36 | 37 | NUM_OUTPUTS 38 | }; 39 | enum LightIds { 40 | NUM_LIGHTS 41 | }; 42 | 43 | dsp::BooleanTrigger themeTrigger; 44 | 45 | dsp::SchmittTrigger pulseTiggerA, pulseTriggerB; 46 | dsp::BooleanTrigger gateTriggerA, gateTriggerB; 47 | dsp::PulseGenerator pulseGeneratorA, pulseGeneratorB; 48 | bool pulseA, pulseB; 49 | 50 | RSMajorTom() { 51 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 52 | 53 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 54 | 55 | configParam(ATT_A_SCALE, -1.f, 1.f, 0.f, "SCALE"); 56 | configParam(ATT_A_OFFSET, -10.f, 10.f, 0.f, "OFFSET"); 57 | configParam(ATT_B_SCALE, -1.f, 1.f, 0.f, "SCALE"); 58 | configParam(ATT_B_OFFSET, -10.f, 10.f, 0.f, "OFFSET"); 59 | 60 | configParam(PULSE_A_BUTTON, 0.f, 1.f, 0.f, "PULSE"); 61 | configParam(GATE_A_BUTTON, 0.f, 1.f, 0.f, "GATE"); 62 | configParam(DOOR_A_BUTTON, 0.f, 1.f, 0.f, "DOOR"); 63 | 64 | configParam(PULSE_B_BUTTON, 0.f, 1.f, 0.f, "PULSE"); 65 | configParam(GATE_B_BUTTON, 0.f, 1.f, 0.f, "GATE"); 66 | configParam(DOOR_B_BUTTON, 0.f, 1.f, 0.f, "DOOR"); 67 | 68 | pulseA = pulseB = false; 69 | } 70 | 71 | void process(const ProcessArgs &args) override { 72 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 73 | RSTheme++; 74 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 75 | } 76 | 77 | // Attenuverters 78 | float cvIn, cvOut; 79 | 80 | cvIn = RSclamp(inputs[ATT_A_IN].getVoltage(),-10.f, 10.f); 81 | cvOut = RSclamp(cvIn * params[ATT_A_SCALE].getValue() + params[ATT_A_OFFSET].getValue(), -10.f, 10.f); 82 | outputs[ATT_A_OUT].setVoltage(cvOut); 83 | 84 | cvIn = RSclamp(inputs[ATT_B_IN].getVoltage(),-10.f, 10.f); 85 | cvOut = RSclamp(cvIn * params[ATT_B_SCALE].getValue() + params[ATT_B_OFFSET].getValue(), -10.f, 10.f); 86 | outputs[ATT_B_OUT].setVoltage(cvOut); 87 | 88 | // Pulses / gates 89 | if(params[DOOR_A_BUTTON].getValue()) pulseA = true; 90 | else { 91 | pulseA = false; 92 | if(pulseTiggerA.process(params[PULSE_A_BUTTON].getValue() > 0.f)) pulseGeneratorA.trigger(1e-3f); 93 | pulseA = pulseGeneratorA.process(args.sampleTime); 94 | if(params[GATE_A_BUTTON].getValue()) pulseA = true; 95 | } 96 | 97 | if(params[DOOR_B_BUTTON].getValue()) pulseB = true; 98 | else { 99 | pulseB = false; 100 | if(pulseTriggerB.process(params[PULSE_B_BUTTON].getValue() > 0.f)) pulseGeneratorB.trigger(1e-3f); 101 | pulseB = pulseGeneratorB.process(args.sampleTime); 102 | if(params[GATE_B_BUTTON].getValue()) pulseB = true; 103 | } 104 | 105 | outputs[PULSEGATE_A_OUT].setVoltage(pulseA ? 10.f : 0.f); 106 | outputs[PULSEGATE_B_OUT].setVoltage(pulseB ? 10.f : 0.f); 107 | } 108 | 109 | json_t* dataToJson() override { 110 | json_t* rootJ = json_object(); 111 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 112 | 113 | return rootJ; 114 | } 115 | 116 | void dataFromJson(json_t* rootJ) override { 117 | json_t* themeJ = json_object_get(rootJ, "theme"); 118 | if(themeJ) RSTheme = json_integer_value(themeJ); 119 | } 120 | }; 121 | 122 | 123 | struct RSMajorTomWidget : ModuleWidget { 124 | RSMajorTom* module; 125 | 126 | int x, y, smlGap, lrgGap, labOfs; 127 | 128 | RSMajorTomWidget(RSMajorTom *module) { 129 | INFO("Racket Science: RSMajorTomWidget()"); 130 | 131 | setModule(module); 132 | this->module = module; 133 | 134 | box.size = Vec(RACK_GRID_WIDTH * 9, RACK_GRID_HEIGHT); 135 | int middle = box.size.x / 2 + 1; 136 | 137 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSMajorTom::THEME_BUTTON)); 138 | 139 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "MAJOR TOM", 14, module)); 140 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12, module)); 141 | 142 | x = 25; y = 50; 143 | smlGap = 30; lrgGap = 65; 144 | labOfs = 20; 145 | 146 | addInput(createInputCentered(Vec(x, y), module, RSMajorTom::ATT_A_IN)); 147 | x += smlGap; y -= labOfs; 148 | addChild(new RSLabelCentered(x, y, "SCALE", 10, module)); 149 | y += labOfs; 150 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::ATT_A_SCALE)); 151 | x += smlGap; y -= labOfs; 152 | addChild(new RSLabelCentered(x, y, "OFFSET", 10, module)); 153 | y += labOfs; 154 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::ATT_A_OFFSET)); 155 | x += smlGap; 156 | addOutput(createOutputCentered(Vec(x, y), module, RSMajorTom::ATT_A_OUT)); 157 | 158 | x = 25; y += smlGap; 159 | addInput(createInputCentered(Vec(x, y), module, RSMajorTom::ATT_B_IN)); 160 | x += smlGap; 161 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::ATT_B_SCALE)); 162 | x += smlGap; 163 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::ATT_B_OFFSET)); 164 | x += smlGap; 165 | addOutput(createOutputCentered(Vec(x, y), module, RSMajorTom::ATT_B_OUT)); 166 | y += smlGap; 167 | 168 | // Pulses / gates 169 | addChild(new RSLabelCentered(middle, y, "PULSES / GATES / DOORS", 10, module)); 170 | x = 25; y += labOfs; 171 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::PULSE_A_BUTTON)); 172 | addChild(new RSLabelCentered(x, y + 3, "PULSE", 10, module)); 173 | x += smlGap; 174 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::GATE_A_BUTTON)); 175 | addChild(new RSLabelCentered(x, y + 3, "GATE", 10, module)); 176 | x += smlGap; 177 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::DOOR_A_BUTTON)); 178 | addChild(new RSLabelCentered(x, y + 3, "DOOR", 10, module)); 179 | x += smlGap; 180 | addOutput(createOutputCentered(Vec(x, y), module, RSMajorTom::PULSEGATE_A_OUT)); 181 | 182 | x = 25; y += smlGap; 183 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::PULSE_B_BUTTON)); 184 | addChild(new RSLabelCentered(x, y + 3, "PULSE", 10, module)); 185 | x += smlGap; 186 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::GATE_B_BUTTON)); 187 | addChild(new RSLabelCentered(x, y + 3, "GATE", 10, module)); 188 | x += smlGap; 189 | addParam(createParamCentered(Vec(x, y), module, RSMajorTom::DOOR_B_BUTTON)); 190 | addChild(new RSLabelCentered(x, y + 3, "DOOR", 10, module)); 191 | x += smlGap; 192 | addOutput(createOutputCentered(Vec(x, y), module, RSMajorTom::PULSEGATE_B_OUT)); 193 | y += smlGap; 194 | 195 | // Whatever goes here 196 | addChild(new RSLabelCentered(middle, y, "something sometihng", 10, module)); 197 | x = 25; y += labOfs; 198 | 199 | 200 | } 201 | 202 | void step() override { 203 | if(!module) return; 204 | 205 | ModuleWidget::step(); 206 | } 207 | 208 | void customDraw(const DrawArgs& args) {} 209 | #include "RSModuleWidgetDraw.hpp" 210 | }; 211 | 212 | Model *modelRSMajorTom = createModel("RSMajorTom"); -------------------------------------------------------------------------------- /src/RSMissionControl.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSMissionControl : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | NUM_OUTPUTS 15 | }; 16 | enum LightIds { 17 | NUM_LIGHTS 18 | }; 19 | 20 | dsp::BooleanTrigger themeTrigger; 21 | 22 | RSMissionControl() { 23 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 24 | 25 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 26 | 27 | } 28 | 29 | void process(const ProcessArgs &args) override { 30 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 31 | RSTheme++; 32 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 33 | } 34 | 35 | } 36 | 37 | json_t* dataToJson() override { 38 | json_t* rootJ = json_object(); 39 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 40 | 41 | return rootJ; 42 | } 43 | 44 | void dataFromJson(json_t* rootJ) override { 45 | json_t* themeJ = json_object_get(rootJ, "theme"); 46 | if(themeJ) RSTheme = json_integer_value(themeJ); 47 | } 48 | }; 49 | 50 | 51 | struct RSMissionControlWidget : ModuleWidget { 52 | RSMissionControl* module; 53 | 54 | RSMissionControlWidget(RSMissionControl *module) { 55 | INFO("Racket Science: RSMissionControlWidget()"); 56 | 57 | setModule(module); 58 | this->module = module; 59 | 60 | box.size = Vec(RACK_GRID_WIDTH * 30, RACK_GRID_HEIGHT); 61 | int middle = box.size.x / 2 + 1; 62 | 63 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSMissionControl::THEME_BUTTON)); 64 | 65 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "MISSION CONTROL", 14, module)); 66 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12, module)); 67 | 68 | } 69 | 70 | void step() override { 71 | if(!module) return; 72 | 73 | ModuleWidget::step(); 74 | } 75 | 76 | void customDraw(const DrawArgs& args) {} 77 | #include "RSModuleWidgetDraw.hpp" 78 | }; 79 | 80 | Model *modelRSMissionControl = createModel("RSMissionControl"); -------------------------------------------------------------------------------- /src/RSModule.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct RSModule : Module { 4 | int RSTheme = 1; 5 | }; 6 | 7 | static void updateRSTheme(int themeIdx) { 8 | RSGlobal.themes[themeIdx].bgColor = nvgHSL(RSGlobal.themes[themeIdx].bghsl.hue, 9 | RSGlobal.themes[themeIdx].bghsl.sat, 10 | RSGlobal.themes[themeIdx].bghsl.lum); 11 | RSGlobal.themes[themeIdx].lbColor = nvgHSL(RSGlobal.themes[themeIdx].lbhsl.hue, 12 | RSGlobal.themes[themeIdx].lbhsl.sat, 13 | RSGlobal.themes[themeIdx].lbhsl.lum); 14 | RSGlobal.themes[themeIdx].ssColor = nvgHSL(RSGlobal.themes[themeIdx].sshsl.hue, 15 | RSGlobal.themes[themeIdx].sshsl.sat, 16 | RSGlobal.themes[themeIdx].sshsl.lum); 17 | float ledSat = 1.0f; 18 | float ledLum = 0.5f; 19 | RSGlobal.themes[themeIdx].lAColor = nvgHSL(RSGlobal.themes[themeIdx].ledAh, ledSat, ledLum); 20 | RSGlobal.themes[themeIdx].lBColor = nvgHSL(RSGlobal.themes[themeIdx].ledBh, ledSat, ledLum); 21 | } 22 | 23 | static void saveRSGlobal() { 24 | INFO("Racket Science: saveRSGlobal()"); 25 | 26 | std::string RSGFile = asset::user("RacketScience/RSGlobal.json");; 27 | FILE *file = fopen(RSGFile.c_str(), "w"); 28 | if(file) { 29 | json_t* rootJ = json_object(); 30 | json_object_set_new(rootJ, "themeCount", json_integer(RSGlobal.themeCount)); 31 | json_object_set_new(rootJ, "themeIdx", json_integer(RSGlobal.themeIdx)); 32 | json_object_set_new(rootJ, "rateDivider", json_integer(RSGlobal.rateDivider)); 33 | json_object_set_new(rootJ, "logLevel", json_integer(RSGlobal.logLevel)); 34 | 35 | for(int theme = 0; theme < RSGlobal.themeCount; theme++) { 36 | json_t* themeJ = json_object(); 37 | json_object_set_new(themeJ, "bghue", json_real(RSGlobal.themes[theme].bghsl.hue)); 38 | json_object_set_new(themeJ, "bgsat", json_real(RSGlobal.themes[theme].bghsl.sat)); 39 | json_object_set_new(themeJ, "bglum", json_real(RSGlobal.themes[theme].bghsl.lum)); 40 | 41 | json_object_set_new(themeJ, "lbhue", json_real(RSGlobal.themes[theme].lbhsl.hue)); 42 | json_object_set_new(themeJ, "lbsat", json_real(RSGlobal.themes[theme].lbhsl.sat)); 43 | json_object_set_new(themeJ, "lblum", json_real(RSGlobal.themes[theme].lbhsl.lum)); 44 | 45 | json_object_set_new(themeJ, "sshue", json_real(RSGlobal.themes[theme].sshsl.hue)); 46 | json_object_set_new(themeJ, "sssat", json_real(RSGlobal.themes[theme].sshsl.sat)); 47 | json_object_set_new(themeJ, "sslum", json_real(RSGlobal.themes[theme].sshsl.lum)); 48 | 49 | json_object_set_new(themeJ, "ledAhue", json_real(RSGlobal.themes[theme].ledAh)); 50 | json_object_set_new(themeJ, "ledBhue", json_real(RSGlobal.themes[theme].ledBh)); 51 | 52 | json_object_set_new(rootJ, ("theme" + std::to_string(theme)).c_str(), themeJ); 53 | } 54 | 55 | json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); 56 | fclose(file); 57 | } 58 | } 59 | 60 | 61 | static void loadRSGlobal() { 62 | INFO("Racket Science: loadRSGlobal()"); 63 | 64 | std::string RSGDir = rack::asset::user("RacketScience/"); 65 | std::string RSGFile = rack::asset::user("RacketScience/RSGlobal.json"); 66 | 67 | if(!rack::system::isDirectory(RSGDir) || !rack::system::isFile(RSGFile)) { 68 | INFO("Racket Science: Creating default themes"); 69 | 70 | rack::system::createDirectory(RSGDir); 71 | 72 | float hue = 0.f; 73 | float hueStep = 1.f / RSGlobal.themeCount; 74 | for(int i = 0; i < RSGlobal.themeCount; i++, hue += hueStep) { 75 | RSGlobal.themes[i].bghsl = {hue, .5f, .3f}; 76 | RSGlobal.themes[i].lbhsl = {hue, .8f, .8f}; 77 | RSGlobal.themes[i].sshsl = {hue, .6f, .7f}; 78 | // LEDs here too once complete 79 | updateRSTheme(i); 80 | } 81 | saveRSGlobal(); 82 | } 83 | else { 84 | FILE *file = fopen(RSGFile.c_str(), "r"); 85 | if(file) { 86 | json_error_t error; 87 | json_t* rootJ = json_loadf(file, 0, &error); 88 | if(!rootJ) { 89 | std::string message = string::f("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); 90 | osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); 91 | } 92 | else { 93 | json_t* themeCountJ = json_object_get(rootJ, "themeCount"); 94 | // if(themeCountJ) RSGlobal.themeCount = json_integer_value(themeCountJ); 95 | json_t* themeIdxJ = json_object_get(rootJ, "themeIdx"); 96 | if(themeIdxJ) RSGlobal.themeIdx = json_integer_value(themeIdxJ); 97 | json_t* rateDividerJ = json_object_get(rootJ, "rateDivider"); 98 | if(rateDividerJ) RSGlobal.rateDivider = json_integer_value(rateDividerJ); 99 | json_t* logLevelJ = json_object_get(rootJ, "logLevel"); 100 | if(logLevelJ) RSGlobal.logLevel = json_integer_value(logLevelJ); 101 | 102 | for(int theme = 0; theme < RSGlobal.themeCount; theme++) { 103 | json_t* themeJ = json_object_get(rootJ, ("theme" + std::to_string(theme)).c_str()); 104 | if(themeJ) { 105 | json_t* bghueJ = json_object_get(themeJ, "bghue"); 106 | if(bghueJ) RSGlobal.themes[theme].bghsl.hue = json_real_value(bghueJ); 107 | json_t* bgsatJ = json_object_get(themeJ, "bgsat"); 108 | if(bgsatJ) RSGlobal.themes[theme].bghsl.sat = json_real_value(bgsatJ); 109 | json_t* bglumJ = json_object_get(themeJ, "bglum"); 110 | if(bglumJ) RSGlobal.themes[theme].bghsl.lum = json_real_value(bglumJ); 111 | 112 | json_t* lbhueJ = json_object_get(themeJ, "lbhue"); 113 | if(lbhueJ) RSGlobal.themes[theme].lbhsl.hue = json_real_value(lbhueJ); 114 | json_t* lbsatJ = json_object_get(themeJ, "lbsat"); 115 | if(lbsatJ) RSGlobal.themes[theme].lbhsl.sat = json_real_value(lbsatJ); 116 | json_t* lblumJ = json_object_get(themeJ, "lblum"); 117 | if(lblumJ) RSGlobal.themes[theme].lbhsl.lum = json_real_value(lblumJ); 118 | 119 | json_t* sshueJ = json_object_get(themeJ, "sshue"); 120 | if(sshueJ) RSGlobal.themes[theme].sshsl.hue = json_real_value(sshueJ); 121 | json_t* sssatJ = json_object_get(themeJ, "sssat"); 122 | if(sssatJ) RSGlobal.themes[theme].sshsl.sat = json_real_value(sssatJ); 123 | json_t* sslumJ = json_object_get(themeJ, "sslum"); 124 | if(sslumJ) RSGlobal.themes[theme].sshsl.lum = json_real_value(sslumJ); 125 | 126 | json_t* ledAhueJ = json_object_get(themeJ, "ledAhue"); 127 | if(ledAhueJ) RSGlobal.themes[theme].ledAh = json_real_value(ledAhueJ); 128 | json_t* ledBhueJ = json_object_get(themeJ, "ledBhue"); 129 | if(ledBhueJ) RSGlobal.themes[theme].ledBh = json_real_value(ledBhueJ); 130 | 131 | updateRSTheme(theme); 132 | } 133 | } 134 | } 135 | fclose(file); 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /src/RSModuleWidgetDraw.hpp: -------------------------------------------------------------------------------- 1 | // Draws panel background instead of using SVG file 2 | // Calls customDraw() should we need to draw anything else 3 | void draw(const DrawArgs& args) override { 4 | nvgStrokeColor(args.vg, COLOR_RS_BRONZE); 5 | 6 | if(module && module->RSTheme > 0) 7 | nvgFillColor(args.vg, RSGlobal.themes[module->RSTheme - 1].bgColor); // Module has own theme 8 | else 9 | nvgFillColor(args.vg, RSGlobal.themes[RSGlobal.themeIdx].bgColor); // Module uses global theme 10 | 11 | if(!module) { // Module browser being populated, pick random theme 12 | nvgFillColor(args.vg, RSGlobal.themes[rand() % RSGlobal.themeCount].bgColor); 13 | } 14 | 15 | nvgStrokeWidth(args.vg, 2); 16 | nvgBeginPath(args.vg); 17 | nvgRoundedRect(args.vg, 1, 1, box.size.x - 1, box.size.y - 1, 5); 18 | nvgStroke(args.vg); 19 | nvgFill(args.vg); 20 | 21 | // Maybe look into gradient background option controlled by Ground Control 22 | 23 | customDraw(args); 24 | 25 | ModuleWidget::draw(args); 26 | } 27 | -------------------------------------------------------------------------------- /src/RSPhaseFour.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSPhaseFour : RSModule { 6 | static const int samples = 1000; 7 | static const int rows = 4; 8 | 9 | enum ParamIds { 10 | THEME_BUTTON, 11 | 12 | ENUMS(SCRUB_KNOBS, rows), 13 | 14 | ENUMS(ENABLE_BUTTONS, rows), 15 | ENUMS(WRITE_BUTTONS, rows), 16 | 17 | ENUMS(CLEAR_BUTTONS, rows), 18 | ENUMS(RAND_BUTTONS, rows), 19 | 20 | ENUMS(DIVIDE_KNOBS, rows), 21 | ENUMS(SELECT_KNOBS, rows), 22 | ENUMS(ADJUST_KNOBS, rows), 23 | ENUMS(MOVE_KNOBS, rows), 24 | 25 | ENUMS(BAKE_BUTTONS, rows), 26 | 27 | NUM_PARAMS 28 | }; 29 | enum InputIds { 30 | ENUMS(PHASE_INS, rows), 31 | 32 | ENUMS(WRITE_INS, rows), 33 | ENUMS(CV_INS, rows), 34 | 35 | ENUMS(CLEAR_INS, rows), 36 | ENUMS(RAND_INS, rows), 37 | 38 | NUM_INPUTS 39 | }; 40 | enum OutputIds { 41 | ENUMS(REC_CV_OUTS, rows), 42 | ENUMS(OVL_CV_OUTS, rows), 43 | 44 | NUM_OUTPUTS 45 | }; 46 | enum LightIds { 47 | NUM_LIGHTS 48 | }; 49 | 50 | 51 | dsp::BooleanTrigger themeTrigger; 52 | 53 | dsp::BooleanTrigger clearTrigger[rows]; 54 | dsp::BooleanTrigger randTrigger[rows]; 55 | dsp::BooleanTrigger bakeTrigger[rows]; 56 | 57 | RSScribbleStrip *ss = NULL; 58 | 59 | float recBuffer[rows][samples] = {}; unsigned int recIdx[rows]; 60 | float ovlBuffer[rows][samples] = {}; unsigned int ovlIdx[rows]; 61 | bool enable[rows], write[rows]; 62 | 63 | float phaseIn[rows], priorPhaseIn[rows]; 64 | unsigned int divide[rows], priorDivide[rows]; 65 | 66 | 67 | RSPhaseFour() { 68 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 69 | 70 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 71 | 72 | for(int row = 0; row < rows; row++) { 73 | configParam(SCRUB_KNOBS + row, -INFINITY, INFINITY, 0.f, "SCRUB"); 74 | 75 | configParam(ENABLE_BUTTONS + row, 0.f, 1.f, 1.f, "WRITE ENABLE"); 76 | configParam(WRITE_BUTTONS + row, 0.f, 1.f, 0.f, "WRITE"); 77 | 78 | configParam(CLEAR_BUTTONS + row, 0.f, 1.f, 0.f, "CLEAR"); 79 | configParam(RAND_BUTTONS + row, 0.f, 1.f, 0.f, "RANDOMIZE"); 80 | 81 | configParam(DIVIDE_KNOBS + row, 1.f, 64.f, 1.f, "DIVIDE"); 82 | configParam(SELECT_KNOBS + row, 1.f, 64.f, 1.f, "SELECT"); 83 | configParam(ADJUST_KNOBS + row, -10.f, 10.f, 0.f, "ADJUST"); 84 | configParam(MOVE_KNOBS + row, -INFINITY, INFINITY, 0.f, "MOVE"); 85 | 86 | recIdx[row] = 0; ovlIdx[row] = 0; 87 | enable[row] = false; write[row] = false; 88 | phaseIn[row] = 0.f; priorPhaseIn[row] = 0.f; 89 | divide[row] = 0; priorDivide[row] = 0; 90 | } 91 | } 92 | 93 | void process(const ProcessArgs &args) override { 94 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 95 | RSTheme++; 96 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 97 | } 98 | 99 | 100 | for(int row = 0; row < rows; row++) { 101 | // Get idx from phase in or scrub knob 102 | if(inputs[PHASE_INS + row].isConnected()) { 103 | phaseIn[row] = RSclamp(inputs[PHASE_INS + row].getVoltage(), 0.f, 10.f); 104 | recIdx[row] = phaseIn[row] * samples / 10.f; 105 | params[SCRUB_KNOBS + row].setValue(phaseIn[row] / 4.15); 106 | } 107 | else { 108 | phaseIn[row] = params[SCRUB_KNOBS + row].getValue(); 109 | recIdx[row] = std::fmod(abs(phaseIn[row] / 2.41), 1.f) * samples; 110 | if(phaseIn[row] < 0.f) recIdx[row] = samples - recIdx[row]; // So negative phase doesn't reverse head direction 111 | } 112 | if(recIdx[row] >= samples) { 113 | INFO("Racket Science: overrun idx=%i", recIdx[row]); 114 | recIdx[row] = samples - 1; // Just in case 115 | } 116 | 117 | // Get CV in 118 | float cvIn = RSclamp(inputs[CV_INS + row].getVoltage(), -10.f, 10.f); 119 | 120 | // Get write from write in or button if enabled 121 | write[row] = false; 122 | if(params[ENABLE_BUTTONS + row].getValue()) { 123 | if(inputs[WRITE_INS + row].isConnected()) write[row] = RSclamp(inputs[WRITE_INS + row].getVoltage(), 0.f, 1.f) ? true : false; 124 | else write[row] = params[WRITE_BUTTONS + row].getValue() ? true : false; 125 | if(write[row]) { 126 | recBuffer[row][recIdx[row]] = cvIn; 127 | // Calc / recalc min / max here 128 | } 129 | } 130 | 131 | // Should do following in widget step() 132 | divide[row] = (int)params[DIVIDE_KNOBS + row].getValue(); 133 | if(divide[row] != priorDivide[row]) updateOverlay(row); 134 | priorDivide[row] = divide[row]; // Else we eat CPU 135 | 136 | if(clearTrigger[row].process(params[CLEAR_BUTTONS + row].getValue() > 0.f)) onClear(row); 137 | if(randTrigger[row].process(params[RAND_BUTTONS + row].getValue() > 0.f)) onRand(row); 138 | 139 | // Bake 140 | if(bakeTrigger[row].process(params[BAKE_BUTTONS + row].getValue() > 0.f)) onBake(row); 141 | 142 | 143 | // rec CV out 144 | outputs[REC_CV_OUTS + row].setVoltage(recBuffer[row][recIdx[row]]); 145 | 146 | // ovl CV out 147 | outputs[OVL_CV_OUTS + row].setVoltage(ovlBuffer[row][recIdx[row]]); // [ovlIdx[row]] 148 | 149 | } 150 | } 151 | 152 | void updateOverlay(int row) { 153 | float step = (float)samples / (float)divide[row]; 154 | for(int i = 0; i < samples - 1; i += step) { 155 | for(int j = i; j < i + step; j++) { 156 | if(j < samples) ovlBuffer[row][j] = recBuffer[row][i]; 157 | } 158 | } 159 | } 160 | 161 | void onClear(int row) { 162 | for(int i = 0; i < samples; i ++) { 163 | recBuffer[row][i] = 0.f; 164 | ovlBuffer[row][i] = 0.f; 165 | } 166 | } 167 | 168 | void onRand(int row) { 169 | std::random_device rd; 170 | std::mt19937 e2(rd()); 171 | std::uniform_real_distribution<> dist(-10.f, 10.f); 172 | for(int i = 0; i < samples; i++) recBuffer[row][i] = dist(e2); 173 | updateOverlay(row); 174 | } 175 | 176 | void onBake(int row) { 177 | for(int i = 0; i < samples; i++) recBuffer[row][i] = ovlBuffer[row][i]; 178 | } 179 | 180 | void onReset() override { 181 | for(int row = 0; row < rows; row++) onClear(row); 182 | } 183 | 184 | void onRandomize() override { 185 | std::random_device rd; 186 | std::mt19937 e2(rd()); 187 | std::uniform_real_distribution<> dist(-10.f, 10.f); 188 | for(int row = 0; row < rows; row++) onRand(row); 189 | } 190 | 191 | json_t* dataToJson() override { 192 | json_t* rootJ = json_object(); 193 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 194 | 195 | if(ss) { 196 | json_t* SS = json_string(ss->text.c_str()); 197 | json_object_set_new(rootJ, "ss", SS); 198 | } 199 | 200 | json_t* recSamples = json_array(); 201 | json_t* ovlSamples = json_array(); 202 | 203 | for(int row = 0; row < rows; row++) { 204 | for(int i = 0; i < samples; i++) { 205 | json_array_append_new(recSamples, json_real(recBuffer[row][i])); 206 | json_array_append_new(ovlSamples, json_real(ovlBuffer[row][i])); 207 | } 208 | } 209 | 210 | json_object_set_new(rootJ, "recSamples", recSamples); 211 | json_object_set_new(rootJ, "ovlSamples", ovlSamples); 212 | 213 | return rootJ; 214 | } 215 | 216 | void dataFromJson(json_t* rootJ) override { 217 | json_t* themeJ = json_object_get(rootJ, "theme"); 218 | if(themeJ) RSTheme = json_integer_value(themeJ); 219 | 220 | json_t* SS = json_object_get(rootJ, "ss"); 221 | if(SS) ss->text = json_string_value(SS); 222 | 223 | json_t* recSamples = json_object_get(rootJ, "recSamples"); 224 | json_t* ovlSamples = json_object_get(rootJ, "ovlSamples"); 225 | 226 | if(recSamples && ovlSamples) { 227 | for(int row = 0; row < rows; row++) { 228 | for(int i = 0; i < samples; i++) { 229 | recBuffer[row][i] = json_number_value(json_array_get(recSamples, i)); 230 | ovlBuffer[row][i] = json_number_value(json_array_get(ovlSamples, i)); 231 | } 232 | } 233 | } 234 | } 235 | }; 236 | 237 | struct RSPhaseDisplay : TransparentWidget { 238 | std::shared_ptr font; 239 | RSPhaseFour *module; 240 | unsigned int row; 241 | unsigned int *idx; 242 | bool *write; 243 | 244 | RSPhaseDisplay(RSPhaseFour *module, int row, int x, int y, int xs, int ys) { 245 | this->module = module; 246 | this->row = row; 247 | this->idx = &module->recIdx[row]; 248 | this->write = &module->write[row]; 249 | 250 | font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/Ubuntu Condensed 400.ttf")); 251 | 252 | box.pos = Vec(x / 2, y / 2); 253 | box.size = Vec(xs, ys); 254 | }; 255 | 256 | void draw(const DrawArgs& args) override { 257 | auto startTime = std::chrono::system_clock::now(); 258 | 259 | nvgFontSize(args.vg, 10); 260 | nvgFontFaceId(args.vg, font->handle); 261 | nvgTextLetterSpacing(args.vg, 0); 262 | 263 | // Bounding box 264 | nvgStrokeColor(args.vg, COLOR_RS_BRONZE); 265 | nvgFillColor(args.vg, COLOR_BLACK); 266 | nvgStrokeWidth(args.vg, 1.5f); 267 | 268 | nvgBeginPath(args.vg); 269 | nvgRoundedRect(args.vg, box.pos.x, box.pos.y, box.size.x, box.size.y, 5); 270 | nvgStroke(args.vg); 271 | nvgFill(args.vg); 272 | 273 | if(!module) { // Stand out in the module browser 274 | nvgFontSize(args.vg, 60); 275 | nvgFillColor(args.vg, COLOR_RS_BRONZE); 276 | nvgTextAlign(args.vg, NVG_ALIGN_CENTER); 277 | 278 | char msg[30]; 279 | switch(this->row) { 280 | case 0: { 281 | std::time_t t = std::time(0); 282 | std::tm* now = std::localtime(&t); 283 | switch(now->tm_wday) { 284 | case 0: strcpy(msg, "Happy Sunday"); break; 285 | case 1: strcpy(msg, "Happy Monday"); break; 286 | case 2: strcpy(msg, "Happy Tuesday"); break; 287 | case 3: strcpy(msg, "Happy Wednesday"); break; 288 | case 4: strcpy(msg, "Happy Thursday"); break; 289 | case 5: strcpy(msg, "Happy Friday"); break; 290 | case 6: strcpy(msg, "Happy Saturday"); break; 291 | } 292 | if(now->tm_mday == 25 && now->tm_mon + 1 == 12) strcpy(msg, "MERRY XMAS!"); 293 | break; 294 | } 295 | case 1: strcpy(msg, "R a c k e t S c i e n c e"); break; 296 | case 2: strcpy(msg, "This space available for rent"); break; 297 | case 3: strcpy(msg, "Please consider donating :)"); break; 298 | } 299 | nvgText(args.vg, box.pos.x + box.size.x / 2, box.pos.y + 50, msg, NULL); 300 | return; 301 | } 302 | 303 | // Center line 304 | int centerLine = box.pos.y + (box.size.y / 2); 305 | nvgStrokeColor(args.vg, COLOR_BLUE); 306 | nvgBeginPath(args.vg); 307 | nvgMoveTo(args.vg, box.pos.x, centerLine); 308 | nvgLineTo(args.vg, box.pos.x + box.size.x, centerLine); 309 | nvgStroke(args.vg); 310 | 311 | // recBuffer 312 | nvgStrokeColor(args.vg, COLOR_GREEN); 313 | nvgBeginPath(args.vg); 314 | nvgMoveTo(args.vg, box.pos.x, centerLine - (module->recBuffer[row][0] / 20 * box.size.y)); 315 | for(int i = 0; i < box.size.x; i++) { 316 | unsigned int idx = module->samples / box.size.x * i; 317 | int val = module->recBuffer[row][idx] / 20.f * box.size.y; 318 | nvgLineTo(args.vg, box.pos.x + i, centerLine - val); 319 | } 320 | nvgStroke(args.vg); 321 | 322 | // ovlBuffer 323 | nvgStrokeColor(args.vg, COLOR_RED); 324 | nvgBeginPath(args.vg); 325 | nvgMoveTo(args.vg, box.pos.x, centerLine - (module->ovlBuffer[row][0] / 20 * box.size.y)); 326 | for(int i = 0; i < box.size.x; i++) { 327 | unsigned int idx = module->samples / box.size.x * i; 328 | int val = module->ovlBuffer[row][idx] / 20 * box.size.y; 329 | nvgLineTo(args.vg, box.pos.x + i, centerLine - val); 330 | } 331 | nvgStroke(args.vg); 332 | 333 | // Divisions 334 | nvgStrokeColor(args.vg, COLOR_WHITE); 335 | for(int i = box.size.x / module->divide[row]; i < box.size.x; i += box.size.x / module->divide[row]) { 336 | nvgBeginPath(args.vg); 337 | nvgMoveTo(args.vg, box.pos.x + i, box.pos.y); 338 | nvgLineTo(args.vg, box.pos.x + i, box.pos.y + box.size.y); 339 | nvgStroke(args.vg); 340 | } 341 | 342 | // Index 343 | nvgStrokeColor(args.vg, *write == true ? COLOR_RED : COLOR_RS_GREY); 344 | nvgStrokeWidth(args.vg, 1.f); 345 | 346 | nvgBeginPath(args.vg); 347 | nvgMoveTo(args.vg, box.pos.x + (box.size.x / module->samples * *idx), box.pos.y); 348 | nvgLineTo(args.vg, box.pos.x + (box.size.x / module->samples * *idx), box.pos.y + box.size.y); 349 | nvgStroke(args.vg); 350 | }; 351 | }; 352 | 353 | struct RSPhaseFourWidget : ModuleWidget { 354 | RSPhaseFour* module; 355 | 356 | int x, y, smlGap, lrgGap, labOfs; 357 | 358 | RSLabelCentered *patternLabel; 359 | 360 | RSPhaseFourWidget(RSPhaseFour *module) { 361 | INFO("Racket Science: RSPhaseFourWidget()"); 362 | 363 | setModule(module); 364 | this->module = module; 365 | 366 | box.size = Vec(RACK_GRID_WIDTH * 97, RACK_GRID_HEIGHT); 367 | int middle = box.size.x / 2 + 1; 368 | 369 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSPhaseFour::THEME_BUTTON)); 370 | 371 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "PHASE IV", 14, module)); 372 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12, module)); 373 | 374 | x = 40; y = 50; 375 | smlGap = 30; lrgGap = 65; 376 | labOfs = 22; 377 | 378 | 379 | // Left side labels 380 | 381 | // Skip over display 382 | 383 | // Right side labels 384 | 385 | 386 | for(int row = 0, rowGap = 90; row < module->rows; row++, y += rowGap) addRow(row, x, y, 70); 387 | } 388 | 389 | void addRow(int row, int x, int y, int h) { 390 | // Add scrub knob & phase in 391 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::SCRUB_KNOBS + row)); 392 | addInput(createInputCentered(Vec(x, y), module, RSPhaseFour::PHASE_INS + row)); 393 | x += lrgGap - 15; 394 | 395 | // Add CV in 396 | addInput(createInputCentered(Vec(x, y), module, RSPhaseFour::CV_INS + row)); 397 | addChild(new RSLabelCentered(x, y + labOfs, "CV IN", 10, module)); 398 | x += smlGap; 399 | 400 | // Add write enable, button & in 401 | y -= 18; 402 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::ENABLE_BUTTONS + row)); 403 | addChild(new RSLabelCentered(x, y + 3, "ENABLE", 9, module)); 404 | y += 30; 405 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::WRITE_BUTTONS + row)); 406 | addInput(createInputCentered(Vec(x, y), module, RSPhaseFour::WRITE_INS + row)); 407 | addChild(new RSLabelCentered(x, y + labOfs, "WRITE")); 408 | x += smlGap; 409 | y -= 30; 410 | 411 | // Add clear & randomize 412 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::CLEAR_BUTTONS + row)); 413 | addChild(new RSLabelCentered(x, y - 14, "CLEAR", 10, module)); 414 | y += 30; 415 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::RAND_BUTTONS + row)); 416 | addChild(new RSLabelCentered(x, y + 22, "RAND", 10, module)); 417 | x += smlGap; 418 | y -= 30; 419 | 420 | // Add divide, select, adjust & move knobs 421 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::DIVIDE_KNOBS + row)); 422 | addChild(new RSLabelCentered(x, y - 14, "DIVIDE", 10, module)); 423 | y += 30; 424 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::ADJUST_KNOBS + row)); 425 | addChild(new RSLabelCentered(x, y + 22, "ADJUST", 10, module)); 426 | x += smlGap; 427 | y -= 30; 428 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::SELECT_KNOBS + row)); 429 | addChild(new RSLabelCentered(x, y - 14, "SELECT", 10, module)); 430 | y += 30; 431 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::MOVE_KNOBS + row)); 432 | addChild(new RSLabelCentered(x, y + 22, "MOVE", 10, module)); 433 | x += smlGap; 434 | 435 | // Add bake buttons 436 | addParam(createParamCentered(Vec(x, y), module, RSPhaseFour::BAKE_BUTTONS + row)); 437 | addChild(new RSLabelCentered(x, y -14, "BAKE", 10, module)); 438 | x += smlGap; 439 | 440 | //x = 370; // So we line up with Fido3 steps 441 | addChild(new RSPhaseDisplay(module, row, x, y - 45, 1040, h)); 442 | x += 1040 + smlGap; 443 | y -= smlGap; 444 | 445 | // rec CV out 446 | addOutput(createOutputCentered(Vec(x, y), module, RSPhaseFour::REC_CV_OUTS + row)); 447 | y += smlGap; 448 | 449 | // ovl CV out 450 | addOutput(createOutputCentered(Vec(x, y), module, RSPhaseFour::OVL_CV_OUTS + row)); 451 | } 452 | 453 | void step() override { 454 | if(!module) return; 455 | 456 | ModuleWidget::step(); 457 | } 458 | 459 | void customDraw(const DrawArgs& args) {} 460 | #include "RSModuleWidgetDraw.hpp" 461 | }; 462 | 463 | Model *modelRSPhaseFour = createModel("RSPhaseFour"); -------------------------------------------------------------------------------- /src/RSShades.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSShades : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | ENUMS(HUE_IN, 12), 12 | ENUMS(SAT_IN, 12), 13 | ENUMS(LUM_IN, 12), 14 | NUM_INPUTS 15 | }; 16 | enum OutputIds { 17 | NUM_OUTPUTS 18 | }; 19 | enum LightIds { 20 | NUM_LIGHTS 21 | }; 22 | 23 | dsp::BooleanTrigger themeTrigger; 24 | 25 | RSShades() { 26 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 27 | 28 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 29 | } 30 | 31 | void process(const ProcessArgs &args) override { 32 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 33 | RSTheme++; 34 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 35 | } 36 | } 37 | 38 | json_t* dataToJson() override { 39 | json_t* rootJ = json_object(); 40 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 41 | 42 | return rootJ; 43 | } 44 | 45 | void dataFromJson(json_t* rootJ) override { 46 | json_t* themeJ = json_object_get(rootJ, "theme"); 47 | if(themeJ) RSTheme = json_integer_value(themeJ); 48 | } 49 | }; 50 | 51 | 52 | struct RSShadesWidget : ModuleWidget { 53 | RSShades* module; 54 | 55 | RSShadesWidget(RSShades *module) { 56 | INFO("Racket Science: RSShadesWidget()"); 57 | 58 | setModule(module); 59 | this->module = module; 60 | 61 | box.size = Vec(RACK_GRID_WIDTH * 10, RACK_GRID_HEIGHT); 62 | int middle = box.size.x / 2 + 1; 63 | 64 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSShades::THEME_BUTTON)); 65 | 66 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "SHADES", 14, module)); 67 | 68 | addChild(new RSLabelCentered( 40, 25, "HUE", 10, module)); 69 | addChild(new RSLabelCentered( 80, 25, "SAT", 10, module)); 70 | addChild(new RSLabelCentered(120, 25, "LUM", 10, module)); 71 | 72 | for(int i = 0; i < 12; i++) { 73 | int offset; 74 | switch(i) { 75 | case 1: case 3: case 5: case 8: case 10: offset = 7; break; 76 | default: offset = -7; 77 | } 78 | addChild(new RSLabelCentered(12, 46 + (i * 28), std::to_string(12 - i).c_str(), 10, module)); 79 | addInput(createInputCentered(Vec( 40 - offset, 42 + (i * 28)), module, RSShades::HUE_IN + i)); 80 | addInput(createInputCentered(Vec( 80 - offset, 42 + (i * 28)), module, RSShades::SAT_IN + i)); 81 | addInput(createInputCentered(Vec(120 - offset, 42 + (i * 28)), module, RSShades::LUM_IN + i)); 82 | } 83 | 84 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12, module)); 85 | } 86 | 87 | void step() override { 88 | if(!module) return; 89 | 90 | for(int i = 0; i < 12; i++) { 91 | if(module->inputs[RSShades::HUE_IN_LAST - i].isConnected()) { 92 | RSGlobal.themes[i + 1].bghsl.hue = RSclamp(module->inputs[RSShades::HUE_IN_LAST - i].getVoltage(), 0.f, 10.f) / 10.f; 93 | RSGlobal.themes[i + 1].bgColor = nvgHSL(RSGlobal.themes[i + 1].bghsl.hue, 94 | RSGlobal.themes[i + 1].bghsl.sat, 95 | RSGlobal.themes[i + 1].bghsl.lum); 96 | } 97 | if(module->inputs[RSShades::SAT_IN_LAST - i].isConnected()) { 98 | RSGlobal.themes[i + 1].bghsl.sat = RSclamp(module->inputs[RSShades::SAT_IN_LAST - i].getVoltage(), 0.f, 10.f) / 10.f; 99 | RSGlobal.themes[i + 1].bgColor = nvgHSL(RSGlobal.themes[i + 1].bghsl.hue, 100 | RSGlobal.themes[i + 1].bghsl.sat, 101 | RSGlobal.themes[i + 1].bghsl.lum); 102 | } 103 | if(module->inputs[RSShades::LUM_IN_LAST - i].isConnected()) { 104 | RSGlobal.themes[i + 1].bghsl.lum = RSclamp(module->inputs[RSShades::LUM_IN_LAST - i].getVoltage(), 0.f, 10.f) / 10.f; 105 | RSGlobal.themes[i + 1].bgColor = nvgHSL(RSGlobal.themes[i + 1].bghsl.hue, 106 | RSGlobal.themes[i + 1].bghsl.sat, 107 | RSGlobal.themes[i + 1].bghsl.lum); 108 | } 109 | } 110 | 111 | ModuleWidget::step(); 112 | } 113 | 114 | void customDraw(const DrawArgs& args) {} 115 | #include "RSModuleWidgetDraw.hpp" 116 | }; 117 | 118 | Model *modelRSShades = createModel("RSShades"); -------------------------------------------------------------------------------- /src/RSSkeleton.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSSkeleton : RSModule { 6 | enum ParamIds { 7 | THEME_BUTTON, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | NUM_OUTPUTS 15 | }; 16 | enum LightIds { 17 | NUM_LIGHTS 18 | }; 19 | 20 | dsp::BooleanTrigger themeTrigger; 21 | 22 | RSSkeleton() { 23 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 24 | 25 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 26 | 27 | } 28 | 29 | void process(const ProcessArgs &args) override { 30 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 31 | RSTheme++; 32 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 33 | } 34 | 35 | } 36 | 37 | json_t* dataToJson() override { 38 | json_t* rootJ = json_object(); 39 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 40 | 41 | return rootJ; 42 | } 43 | 44 | void dataFromJson(json_t* rootJ) override { 45 | json_t* themeJ = json_object_get(rootJ, "theme"); 46 | if(themeJ) RSTheme = json_integer_value(themeJ); 47 | } 48 | }; 49 | 50 | 51 | struct RSSkeletonWidget : ModuleWidget { 52 | RSSkeleton* module; 53 | 54 | RSSkeletonWidget(RSSkeleton *module) { 55 | INFO("Racket Science: RSSkeletonWidget()"); 56 | 57 | setModule(module); 58 | this->module = module; 59 | 60 | box.size = Vec(RACK_GRID_WIDTH * 3, RACK_GRID_HEIGHT); 61 | int middle = box.size.x / 2 + 1; 62 | 63 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSSkeleton::THEME_BUTTON)); 64 | 65 | addChild(new RSLabelCentered(middle, box.pos.y + 13, "TITLE1", 14, module)); 66 | addChild(new RSLabelCentered(middle, box.pos.y + 25, "TITLE2", 14, module)); 67 | addChild(new RSLabelCentered(middle, box.pos.y + 37, "TITLE3", 14, module)); 68 | 69 | addChild(new RSLabelCentered(middle, box.size.y - 15, "Racket", 12, module)); 70 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Science", 12, module)); 71 | 72 | } 73 | 74 | void step() override { 75 | if(!module) return; 76 | 77 | ModuleWidget::step(); 78 | } 79 | 80 | void customDraw(const DrawArgs& args) {} 81 | #include "RSModuleWidgetDraw.hpp" 82 | }; 83 | 84 | Model *modelRSSkeleton = createModel("RSSkeleton"); -------------------------------------------------------------------------------- /src/RSVectorVictor.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | 6 | struct RSVectorVictor : Module { 7 | enum ParamIds { 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | PHASEA_INPUT, 12 | WRITEA_INPUT, 13 | INA_INPUT, 14 | PHASEB_INPUT, 15 | WRITEB_INPUT, 16 | INB_INPUT, 17 | NUM_INPUTS 18 | }; 19 | enum OutputIds { 20 | OUTA_OUTPUT, 21 | OUTB_OUTPUT, 22 | NUM_OUTPUTS 23 | }; 24 | enum LightIds { 25 | WRITEA_LIGHT, 26 | WRITEB_LIGHT, 27 | NUM_LIGHTS 28 | }; 29 | 30 | RSVectorVictor() { 31 | INFO("Racket Science: %i params %i inputs %i outputs %i lights", NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 32 | 33 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 34 | } 35 | 36 | #define SAMPLES 1000 37 | float bufferA[SAMPLES] = {}; 38 | float bufferB[SAMPLES] = {}; 39 | 40 | void onReset() override { 41 | std::memset(bufferA, 0, sizeof(bufferA)); 42 | std::memset(bufferB, 0, sizeof(bufferB)); 43 | } 44 | 45 | void process(const ProcessArgs &args) override { 46 | unsigned int idxA, idxB; 47 | float inA, inB; 48 | 49 | idxA = (unsigned int)abs(inputs[PHASEA_INPUT].getVoltage() * SAMPLES / 10); if(idxA > SAMPLES - 1) idxA = SAMPLES - 1; 50 | idxB = (unsigned int)abs(inputs[PHASEB_INPUT].getVoltage() * SAMPLES / 10); if(idxB > SAMPLES - 1) idxB = SAMPLES - 1; 51 | 52 | inA = inputs[INA_INPUT].getVoltage(); 53 | inB = inputs[INB_INPUT].getVoltage(); 54 | 55 | if(inputs[WRITEA_INPUT].getVoltage()) bufferA[idxA] = inA; 56 | if(inputs[WRITEB_INPUT].getVoltage()) bufferB[idxB] = inB; 57 | 58 | outputs[OUTA_OUTPUT].setVoltage(bufferA[idxA]); 59 | outputs[OUTB_OUTPUT].setVoltage(bufferB[idxB]); 60 | 61 | lights[WRITEA_LIGHT].setSmoothBrightness(inputs[WRITEA_INPUT].getVoltage() ? 1 : 0, 10); 62 | lights[WRITEB_LIGHT].setSmoothBrightness(inputs[WRITEB_INPUT].getVoltage() ? 1 : 0, 10); 63 | } 64 | 65 | json_t* dataToJson() override { 66 | INFO("Racket Science: d2j()"); 67 | json_t* rootJ = json_object(); 68 | 69 | json_t* samplesAJ = json_array(); 70 | json_t* samplesBJ = json_array(); 71 | 72 | for(int i = 0; i < SAMPLES; i++) { 73 | json_array_append_new(samplesAJ, json_real(bufferA[i])); 74 | json_array_append_new(samplesBJ, json_real(bufferB[i])); 75 | } 76 | 77 | json_object_set_new(rootJ, "samplesA", samplesAJ); 78 | json_object_set_new(rootJ, "samplesB", samplesBJ); 79 | 80 | return rootJ; 81 | } 82 | 83 | void dataFromJson(json_t* rootJ) override { 84 | INFO("Racket Science: dfj()"); 85 | json_t* samplesAJ = json_object_get(rootJ, "samplesA"); 86 | json_t* samplesBJ = json_object_get(rootJ, "samplesB"); 87 | 88 | if(samplesAJ) { 89 | for(int i = 0; i < SAMPLES; i++) { 90 | bufferA[i] = json_number_value(json_array_get(samplesAJ, i)); 91 | bufferB[i] = json_number_value(json_array_get(samplesBJ, i)); 92 | } 93 | } 94 | } 95 | }; 96 | 97 | 98 | struct RSVectorVictorWidget : ModuleWidget { 99 | RSVectorVictor *module; 100 | 101 | RSVectorVictorWidget(RSVectorVictor *module) { 102 | INFO("Racket Science: RSVectorVictorWidget()"); 103 | 104 | setModule(module); 105 | this->module = module; 106 | 107 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/RSVectorVictor.svg"))); 108 | 109 | addInput(createInputCentered(mm2px(Vec(7.398, 33.299)), module, RSVectorVictor::PHASEA_INPUT)); 110 | addInput(createInputCentered(mm2px(Vec(18.102, 33.299)), module, RSVectorVictor::WRITEA_INPUT)); 111 | addInput(createInputCentered(mm2px(Vec(7.398, 52.578)), module, RSVectorVictor::INA_INPUT)); 112 | addInput(createInputCentered(mm2px(Vec(7.393, 80.144)), module, RSVectorVictor::PHASEB_INPUT)); 113 | addInput(createInputCentered(mm2px(Vec(18.097, 80.144)), module, RSVectorVictor::WRITEB_INPUT)); 114 | addInput(createInputCentered(mm2px(Vec(7.393, 99.422)), module, RSVectorVictor::INB_INPUT)); 115 | 116 | addOutput(createOutputCentered(mm2px(Vec(18.102, 52.578)), module, RSVectorVictor::OUTA_OUTPUT)); 117 | addOutput(createOutputCentered(mm2px(Vec(18.097, 99.422)), module, RSVectorVictor::OUTB_OUTPUT)); 118 | 119 | addChild(createLightCentered>(mm2px(Vec(18.102, 23.655)), module, RSVectorVictor::WRITEA_LIGHT)); 120 | addChild(createLightCentered>(mm2px(Vec(18.097, 70.5)), module, RSVectorVictor::WRITEB_LIGHT)); 121 | 122 | INFO("Racket Science: exiting RSVectorVictorWidget()"); 123 | } 124 | }; 125 | 126 | 127 | Model *modelRSVectorVictor = createModel("RSVectorVictor"); 128 | -------------------------------------------------------------------------------- /src/RSXYGLR.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RS.hpp" 4 | 5 | struct RSXYGLR : RSModule { 6 | static const int samples = 1000; 7 | 8 | enum ParamIds { 9 | THEME_BUTTON, 10 | CLEAR_BUTTON, 11 | NUM_PARAMS 12 | }; 13 | enum InputIds { 14 | PHASE_IN, 15 | X_IN, 16 | Y_IN, 17 | G_IN, 18 | NUM_INPUTS 19 | }; 20 | enum OutputIds { 21 | X_OUT, 22 | Y_OUT, 23 | G_OUT, 24 | NUM_OUTPUTS 25 | }; 26 | enum LightIds { 27 | NUM_LIGHTS 28 | }; 29 | 30 | dsp::BooleanTrigger themeTrigger; 31 | 32 | float x[samples] = {}; 33 | float y[samples] = {}; 34 | bool g[samples] = {}; 35 | 36 | float phaseIn, xIn, yIn; 37 | bool gIn; 38 | unsigned int idx = 0; 39 | 40 | RSXYGLR() { 41 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 42 | 43 | configParam(THEME_BUTTON, 0.f, 1.f, 0.f, "THEME"); 44 | 45 | configParam(CLEAR_BUTTON, 0.f, 1.f, 0.f, "CLEAR"); 46 | } 47 | 48 | void process(const ProcessArgs &args) override { 49 | if(themeTrigger.process(params[THEME_BUTTON].getValue())) { 50 | RSTheme++; 51 | if(RSTheme > RSGlobal.themeCount) RSTheme = 1; 52 | } 53 | 54 | xIn = RSclamp(inputs[X_IN].getVoltage(), 0.f, 10.f); 55 | yIn = RSclamp(inputs[Y_IN].getVoltage(), 0.f, 10.f); 56 | gIn = inputs[G_IN].getVoltage() > 0.f ? true : false; 57 | 58 | if(inputs[PHASE_IN].isConnected()) { 59 | phaseIn = RSclamp(inputs[PHASE_IN].getVoltage(), 0.f, 10.f); 60 | idx = phaseIn * samples / 10.f; 61 | if(idx >= samples) idx = samples - 1; 62 | 63 | if(gIn) { 64 | x[idx] = xIn; 65 | y[idx] = yIn; 66 | g[idx] = gIn; 67 | } 68 | 69 | outputs[X_OUT].setVoltage(x[idx]); 70 | outputs[Y_OUT].setVoltage(y[idx]); 71 | outputs[G_OUT].setVoltage(g[idx] ? 10.f : 0.f); 72 | } 73 | else { 74 | outputs[X_OUT].setVoltage(xIn); 75 | outputs[Y_OUT].setVoltage(yIn); 76 | outputs[G_OUT].setVoltage(gIn); 77 | } 78 | 79 | if(params[CLEAR_BUTTON].getValue()) onReset(); 80 | } 81 | 82 | void onReset() override { 83 | std::memset(x, 0, sizeof(x)); 84 | std::memset(y, 0, sizeof(y)); 85 | std::memset(g, 0, sizeof(g)); 86 | } 87 | 88 | json_t* dataToJson() override { 89 | json_t* rootJ = json_object(); 90 | 91 | json_object_set_new(rootJ, "theme", json_integer(RSTheme)); 92 | 93 | json_t* xSamples = json_array(); 94 | json_t* ySamples = json_array(); 95 | json_t* gSamples = json_array(); 96 | 97 | for(int i = 0; i < samples; i++) { 98 | json_array_append_new(xSamples, json_real(x[i])); 99 | json_array_append_new(ySamples, json_real(y[i])); 100 | json_array_append_new(gSamples, json_boolean(g[i])); 101 | } 102 | 103 | json_object_set_new(rootJ, "x", xSamples); 104 | json_object_set_new(rootJ, "y", ySamples); 105 | json_object_set_new(rootJ, "g", gSamples); 106 | 107 | return rootJ; 108 | } 109 | 110 | void dataFromJson(json_t* rootJ) override { 111 | json_t* themeJ = json_object_get(rootJ, "theme"); 112 | 113 | if(themeJ) RSTheme = json_integer_value(themeJ); 114 | 115 | json_t* xSamples = json_object_get(rootJ, "x"); 116 | json_t* ySamples = json_object_get(rootJ, "y"); 117 | json_t* gSamples = json_object_get(rootJ, "g"); 118 | 119 | if(xSamples && ySamples && gSamples) { 120 | for(int i = 0; i < samples; i++) { 121 | x[i] = json_number_value(json_array_get(xSamples, i)); 122 | y[i] = json_number_value(json_array_get(ySamples, i)); 123 | g[i] = json_boolean_value(json_array_get(gSamples, i)); 124 | } 125 | } 126 | } 127 | }; 128 | 129 | struct RSTouchPadDisplay : TransparentWidget { 130 | RSXYGLR *module; 131 | 132 | RSTouchPadDisplay(RSXYGLR *module, int x, int y, int xs, int ys) { 133 | this->module = module; 134 | 135 | box.pos = Vec(x / 2, y / 2); 136 | box.size = Vec(xs, ys); 137 | }; 138 | 139 | void draw(const DrawArgs& args) override { 140 | // Bounding box 141 | nvgStrokeColor(args.vg, COLOR_RS_BRONZE); 142 | nvgFillColor(args.vg, COLOR_BLACK); 143 | nvgStrokeWidth(args.vg, 1.5f); 144 | 145 | nvgBeginPath(args.vg); 146 | nvgRoundedRect(args.vg, box.pos.x, box.pos.y, box.size.x, box.size.y, 5); 147 | nvgStroke(args.vg); 148 | nvgFill(args.vg); 149 | 150 | if(!module) return; 151 | 152 | // Indicate last reported position 153 | nvgFillColor(args.vg, module->gIn ? COLOR_RED : COLOR_GREEN); 154 | nvgBeginPath(args.vg); 155 | nvgCircle(args.vg, module->xIn * (box.size.x / 12.f) + 30, box.size.y - (module->yIn * (box.size.y / 12.f)) -5, 10.f); 156 | nvgStroke(args.vg); 157 | nvgFill(args.vg); 158 | 159 | // Indicate phase indexed position 160 | if(module->g[module->idx]) { 161 | nvgFillColor(args.vg, COLOR_RED); 162 | nvgBeginPath(args.vg); 163 | nvgCircle(args.vg, module->x[module->idx] * (box.size.x / 12.f) + 30, box.size.y - (module->y[module->idx] * (box.size.y / 12.f)) - 5, 10.f); 164 | nvgStroke(args.vg); 165 | nvgFill(args.vg); 166 | } 167 | }; 168 | }; 169 | 170 | struct RSXYGBufferDisplay : TransparentWidget { 171 | RSXYGLR *module; 172 | 173 | RSXYGBufferDisplay(RSXYGLR *module, int x, int y, int xs, int ys) { 174 | this->module = module; 175 | 176 | box.pos = Vec(x / 2, y / 2); 177 | box.size = Vec(xs, ys); 178 | }; 179 | 180 | void draw(const DrawArgs& args) override { 181 | // Bounding box 182 | nvgStrokeColor(args.vg, COLOR_RS_BRONZE); 183 | nvgFillColor(args.vg, COLOR_BLACK); 184 | nvgStrokeWidth(args.vg, 1.5f); 185 | 186 | nvgBeginPath(args.vg); 187 | nvgRoundedRect(args.vg, box.pos.x, box.pos.y, box.size.x, box.size.y, 5); 188 | nvgStroke(args.vg); 189 | nvgFill(args.vg); 190 | 191 | if(!module) return; 192 | 193 | // Display g buffer contents 194 | nvgStrokeColor(args.vg, COLOR_RED); 195 | nvgBeginPath(args.vg); 196 | for(int i = 0; i < box.size.x; i++) { 197 | unsigned int idx = module->samples / box.size.x * i; 198 | if(module->g[idx]) { 199 | nvgMoveTo(args.vg, box.pos.x + i, box.pos.y); 200 | nvgLineTo(args.vg, box.pos.x + i, box.pos.y + box.size.y); 201 | } 202 | } 203 | nvgStroke(args.vg); 204 | 205 | // Display X buffer contents 206 | nvgStrokeColor(args.vg, COLOR_GREEN); 207 | nvgBeginPath(args.vg); 208 | nvgMoveTo(args.vg, box.pos.x, box.pos.y + box.size.y); 209 | for(int i = 0; i < box.size.x; i++) { 210 | unsigned int idx = module->samples / box.size.x * i; 211 | nvgLineTo(args.vg, box.pos.x + i, box.pos.y + box.size.y - (module->x[idx] / 10 * box.size.y)); 212 | } 213 | nvgStroke(args.vg); 214 | 215 | // Display Y buffer contents 216 | nvgStrokeColor(args.vg, COLOR_BLUE); 217 | nvgBeginPath(args.vg); 218 | nvgMoveTo(args.vg, box.pos.x, box.pos.y + box.size.y); 219 | for(int i = 0; i < box.size.x; i++) { 220 | unsigned int idx = module->samples / box.size.x * i; 221 | nvgLineTo(args.vg, box.pos.x + i, box.pos.y + box.size.y - (module->y[idx] / 10 * box.size.y)); 222 | } 223 | nvgStroke(args.vg); 224 | 225 | // Indicate buffer index 226 | nvgStrokeColor(args.vg, module->gIn ? COLOR_RED : COLOR_RS_GREY); 227 | nvgStrokeWidth(args.vg, 1.f); 228 | nvgBeginPath(args.vg); 229 | nvgMoveTo(args.vg, box.pos.x + (box.size.x / module->samples * module->idx), box.pos.y); 230 | nvgLineTo(args.vg, box.pos.x + (box.size.x / module->samples * module->idx), box.pos.y + box.size.y); 231 | nvgStroke(args.vg); 232 | 233 | nvgStrokeColor(args.vg, COLOR_RS_BRONZE); 234 | nvgBeginPath(args.vg); 235 | nvgRoundedRect(args.vg, box.pos.x, box.pos.y, box.size.x, box.size.y, 5); 236 | nvgStroke(args.vg); 237 | }; 238 | }; 239 | 240 | struct RSXYGLRWidget : ModuleWidget { 241 | RSXYGLR* module; 242 | Widget* panelBorder; 243 | 244 | RSXYGLRWidget(RSXYGLR *module) { 245 | INFO("Racket Science: RSXYGLRWidget()"); 246 | 247 | setModule(module); 248 | this->module = module; 249 | 250 | panelBorder = new PanelBorder; 251 | addChild(panelBorder); 252 | 253 | box.size = Vec(RACK_GRID_WIDTH * 20, RACK_GRID_HEIGHT); 254 | int middle = box.size.x / 2 + 1; 255 | int third = box.size.x / 3; 256 | 257 | addParam(createParamCentered(Vec(box.pos.x + 5, box.pos.y + 5), module, RSXYGLR::THEME_BUTTON)); 258 | 259 | addChild(new RSLabelCentered(middle, box.pos.y + 14, "XYGLR - Touchpad Loop Recorder", 15, module)); 260 | //addChild(new RSLabelCentered(middle, box.pos.y + 30, "Touchpad Loop Recorder", 14)); 261 | addChild(new RSLabelCentered(middle, box.size.y - 4, "Racket Science", 12, module)); // >= 4HP 262 | //addChild(new RSLabelCentered(middle, box.size.y - 15, "Racket", 12)); 263 | //addChild(new RSLabelCentered(middle, box.size.y - 4, "Science", 12)); 264 | 265 | int x = 30, y = box.size.y - 50; 266 | 267 | addChild(new RSLabelCentered(x, y, "PHASE", 10)); 268 | addInput(createInputCentered(Vec(x, y + 20), module, RSXYGLR::PHASE_IN)); 269 | 270 | x += 40; 271 | addChild(new RSLabelCentered(x, y, "CLEAR", 10)); 272 | addParam(createParamCentered(Vec(x, y + 20), module, RSXYGLR::CLEAR_BUTTON)); 273 | 274 | x += 40; 275 | addChild(new RSLabelCentered(x, y, "X", 10)); 276 | addInput(createInputCentered(Vec(x, y + 20), module, RSXYGLR::X_IN)); 277 | 278 | x += 30; 279 | addChild(new RSLabelCentered(x, y, "Y", 10)); 280 | addInput(createInputCentered(Vec(x, y + 20), module, RSXYGLR::Y_IN)); 281 | 282 | x += 30; 283 | addChild(new RSLabelCentered(x, y, "G", 10)); 284 | addInput(createInputCentered(Vec(x, y + 20), module, RSXYGLR::G_IN)); 285 | 286 | x += 40; 287 | addChild(new RSLabelCentered(x, y, "X", 10)); 288 | addOutput(createOutputCentered(Vec(x, y + 20), module, RSXYGLR::X_OUT)); 289 | 290 | x += 30; 291 | addChild(new RSLabelCentered(x, y, "Y", 10)); 292 | addOutput(createOutputCentered(Vec(x, y + 20), module, RSXYGLR::Y_OUT)); 293 | 294 | x += 30; 295 | addChild(new RSLabelCentered(x, y, "G", 10)); 296 | addOutput(createOutputCentered(Vec(x, y + 20), module, RSXYGLR::G_OUT)); 297 | 298 | addChild(new RSTouchPadDisplay(module, 20, 20, 260, 200)); 299 | addChild(new RSXYGBufferDisplay(module, 20, 240, 260, 70)); 300 | } 301 | 302 | void step() override { 303 | if(!module) return; 304 | 305 | ModuleWidget::step(); 306 | } 307 | 308 | void customDraw(const DrawArgs& args) {} 309 | #include "RSModuleWidgetDraw.hpp" 310 | }; 311 | 312 | Model *modelRSXYGLR = createModel("RSXYGLR"); -------------------------------------------------------------------------------- /src/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "RSModule.hpp" 4 | 5 | rsglobal RSGlobal; 6 | 7 | Plugin *pluginInstance; 8 | 9 | void init(Plugin *p) { 10 | pluginInstance = p; 11 | 12 | // Add modules here 13 | // Order dicates order they will show in module browser 14 | p->addModel(modelRSGroundControl); 15 | p->addModel(modelRSMajorTom); 16 | p->addModel(modelRSHeat); 17 | p->addModel(modelRSReheat); 18 | p->addModel(modelRSCVHeat); 19 | p->addModel(modelRSBoogieBay); 20 | p->addModel(modelRSBoogieBayH8); 21 | p->addModel(modelRSMFH); 22 | p->addModel(modelRSBlank); 23 | p->addModel(modelRSShades); 24 | p->addModel(modelRSFido316); 25 | p->addModel(modelRSPhaseOne); 26 | p->addModel(modelRSPhaseFour); 27 | p->addModel(modelRSMissionControl); 28 | p->addModel(modelRSLaunchControl); 29 | p->addModel(modelRSXYGLR); 30 | 31 | p->addModel(modelRSVectorVictor); 32 | 33 | // Any other plugin initialization may go here. 34 | // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. 35 | 36 | loadRSGlobal(); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/plugin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct rshsl { 5 | float hue; 6 | float sat; 7 | float lum; 8 | }; 9 | 10 | struct rstheme { 11 | // We store HSL so we can set HSL knobs when switching theme without messing around converting to RGB etc 12 | struct rshsl bghsl; 13 | struct rshsl lbhsl; 14 | struct rshsl sshsl; 15 | float ledAh; 16 | float ledBh; 17 | // Then we also store the actual colors, which are updated using updateRSTheme 18 | NVGcolor bgColor; 19 | NVGcolor lbColor; 20 | NVGcolor ssColor; 21 | NVGcolor lAColor; 22 | NVGcolor lBColor; 23 | }; 24 | 25 | struct rsglobal { 26 | static const int themeCount = 16; 27 | int themeIdx; 28 | rstheme themes[themeCount]; 29 | int logLevel; 30 | int rateDivider; 31 | }; 32 | 33 | extern rsglobal RSGlobal; 34 | 35 | using namespace rack; 36 | 37 | // Declare the Plugin, defined in plugin.cpp 38 | extern Plugin *pluginInstance; 39 | 40 | // Declare each Model, defined in each module source file 41 | extern Model *modelRSVectorVictor; 42 | 43 | extern Model *modelRSGroundControl; 44 | extern Model *modelRSMajorTom; 45 | extern Model *modelRSHeat; 46 | extern Model *modelRSReheat; 47 | extern Model *modelRSCVHeat; 48 | extern Model *modelRSBoogieBay; 49 | extern Model *modelRSBoogieBayH8; 50 | extern Model *modelRSMFH; 51 | extern Model *modelRSBlank; 52 | extern Model *modelRSShades; 53 | extern Model *modelRSFido316; 54 | extern Model *modelRSPhaseOne; 55 | extern Model *modelRSPhaseFour; 56 | extern Model *modelRSMissionControl; 57 | extern Model *modelRSLaunchControl; 58 | extern Model *modelRSXYGLR; 59 | --------------------------------------------------------------------------------