├── res ├── SEQEuclid.png ├── Segment7Standard.ttf └── OFL License - Segment7Standard.txt ├── LICENSE ├── README.md └── src └── SEQEuclid.cpp /res/SEQEuclid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrLumps/SEQ-Euclid/HEAD/res/SEQEuclid.png -------------------------------------------------------------------------------- /res/Segment7Standard.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrLumps/SEQ-Euclid/HEAD/res/Segment7Standard.ttf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 MrLumps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SEQ-Euclid 2 | 3 | A 4 channel Euclidian sequencer for [VCV Rack](vcvrack.com) based on the SEQ3 built in sequencer. 4 | 5 | This uses ideas from the paper [The Euclidean algorithm generates traditional musical rhythms](http://cgm.cs.mcgill.ca/~godfried/rhythm-and-mathematics.html) from Proceedings of BRIDGES: Mathematical Connections in Art, Music, and Science by Godfried T. Toussaint. 6 | 7 | If you are interested in trying this yourself I would recommend looking at the Bjorklund paper The Theory of Rep-Rate Pattern Generation in the SNS Timing System [SNS-NOTE-CNTRL-99](https://www.google.ca/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwjnms7w0vPWAhWlx4MKHafnCJQQFggpMAA&url=https%3A%2F%2Fpdfs.semanticscholar.org%2Fc652%2Fd0a32895afc5d50b6527447824c31a553659.pdf&usg=AOvVaw1CzsXZMPaPY938Z1PG5zBC) for implementation information. 8 | 9 | 10 | ## Building 11 | 12 | This has been built against vcvrack 0.4.0, but I've got a non-standard build environment and won't package this up differently until I can work up the energy to get MinGW going so you will need to integrate this yourself. 13 | 14 | In the init function for whatever plugin you integrate this with you will need to include a line like: 15 | 16 | createModel(plugin, "SEQE", "SEQ-Euclid"); 17 | 18 | In Fundimental.cpp for the fundimental plugins, or Core.cpp for the core system etc. 19 | 20 | You will also need to add a block like: 21 | 22 | struct SEQEuclidWidget : ModuleWidget { 23 | SEQEuclidWidget(); 24 | }; 25 | 26 | Into the plugin header file, eg: Fundimental.hpp / core.hpp / wherever as above and add this header to SEQEuclid.cpp. 27 | 28 | You may also wish to adjust the BG_IMAGE_FILE and FONT_FILE macros in SEQEuclid.cpp. 29 | 30 | If you're here you're probably building this yourself, starting this up with no settings.json file will result in the probability controls defaulting to 0 and no signals happening. You will want to turn these to the far right. 31 | 32 | 33 | ## Usage 34 | The basic idea is that you select a bank to use, enter a pattern fill amount, a pattern length, a probability amount and wire the output to something that needs gates or triggers. 35 | 36 | The algorithm created by Bjorklun that's created these patterns will take pattern length and evenly place fill amount of beats in it. 37 | 38 | For example 5 and 7 will result in a pattern of 1011011 while 5 and 12 results with 100101001010. 39 | 40 | If fill is greater than length the sequencer will output nothing but beats at the given BPM eg 1111111.... 41 | 42 | You can make things more interesting by using the probabilty and jog controls. 43 | The probably knob at far right will allow all beats to pass, at 12 noon 50% of beats and far left 0 beats. 44 | The jog control will allow you to step that bank's pattern forward by one step to allow you to offset patterns. 45 | 46 | 47 | ### BPM 48 | Top left is a BMP indicator and control knob. Use to set speed. 49 | 50 | ### Clock In 51 | You can wire up an external clock source to the input under the BPM next to the clock icon 52 | 53 | ### Reset 54 | Reset either by trigger signal or button push will reset all internal counters to 0. This has the effect of starting all banks off at the beginning of their sequences. 55 | This is useful if you want to be sure your patterns are lined up as you expect. 56 | 57 | ### Gate Length 58 | This will allow you to change the length of the gates sent by the sequencer. At full right close beats eg patterns like 11101 the gate signals will bleed into each other. Dial back for individual beats. I found this handy for driving the modal synth. 59 | 60 | ### Sequencer Banks 61 | 62 | #### Fill Display 63 | Shows current fill amount 64 | 65 | #### Fill Control 66 | Allows changing the fill amount from 0 to 256 67 | 68 | #### Length Display 69 | Shows current fill amount 70 | 71 | #### Length Control 72 | Allows changing the length amount from 0 to 256 73 | 74 | #### Probability Control 75 | Allows changing the % chance that a beat will be sent out. Far left 0% far right 100%. 76 | 77 | #### Gate Out 78 | Sends gate signals out 79 | 80 | #### Trigger Out 81 | Sends trigger signals out 82 | 83 | ### Summed Outputs 84 | 85 | #### Gate Sum Out 86 | If there is any gate active in banks 1 through 4 a gate will be sent. 87 | 88 | 89 | #### Trigger Sum Out 90 | If there is any trigger active in banks 1 through 4 a gate will be sent. 91 | 92 | ### Blinky Light 93 | This will blink for the duration of each gate signal. 94 | 95 | -------------------------------------------------------------------------------- /res/OFL License - Segment7Standard.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Cedric Knight , 2 | with Reserved Font Name "Segment7". 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /src/SEQEuclid.cpp: -------------------------------------------------------------------------------- 1 | // Todo make smaller lookup table 2 | // context menu stuff 3 | #include 4 | #include 5 | 6 | //Change to suit wherever you place this 7 | //#include "Fundamental.hpp" 8 | 9 | #include "dsp/digital.hpp" 10 | 11 | #include "erBitData.h" 12 | 13 | #define BG_IMAGE_FILE assetPlugin(plugin, "res/SEQEuclid.png") 14 | #define FONT_FILE assetPlugin(plugin, "res/Segment7Standard.ttf") 15 | 16 | 17 | 18 | struct SEQEuclid : Module { 19 | enum ParamIds { 20 | BPM_PARAM, 21 | RESET_BUTTON, 22 | GATE_LENGTH_PARAM, 23 | PROB1_PARAM, 24 | PROB2_PARAM, 25 | PROB3_PARAM, 26 | PROB4_PARAM, 27 | FILL1_PARAM, 28 | FILL2_PARAM, 29 | FILL3_PARAM, 30 | FILL4_PARAM, 31 | LENGTH1_PARAM, 32 | LENGTH2_PARAM, 33 | LENGTH3_PARAM, 34 | LENGTH4_PARAM, 35 | JOG1_BUTTON, 36 | JOG2_BUTTON, 37 | JOG3_BUTTON, 38 | JOG4_BUTTON, 39 | NUM_PARAMS 40 | }; 41 | enum InputIds { 42 | EXT_CLOCK_INPUT, 43 | RESET_INPUT, 44 | NUM_INPUTS 45 | }; 46 | enum OutputIds { 47 | GATE_OR_OUTPUT, 48 | TRIGGER_OR_OUTPUT, 49 | GATE1_OUTPUT, 50 | GATE2_OUTPUT, 51 | GATE3_OUTPUT, 52 | GATE4_OUTPUT, 53 | TRIGGER1_OUTPUT, 54 | TRIGGER2_OUTPUT, 55 | TRIGGER3_OUTPUT, 56 | TRIGGER4_OUTPUT, 57 | NUM_OUTPUTS 58 | }; 59 | 60 | // LCG see numerical recipies and wikipedia 61 | // The std random engine seems inapropreate for this application 62 | // due to it's construction you'd need to fool with creating / destroying 63 | // distribution fuctions in the audio hot path, which seems unwise, 64 | struct Lcg { 65 | uint_fast32_t seed; 66 | 67 | explicit Lcg() { 68 | Lcg(738); 69 | } 70 | 71 | inline explicit Lcg(uint_fast32_t seed) : seed(seed) { 72 | int32(); 73 | } 74 | 75 | inline uint_fast32_t int32() { 76 | return seed = (seed >> 1) ^ (-(signed int)(seed & 1u) & 0xD0000001u); 77 | } 78 | 79 | // float 0 - 1 80 | inline float flt() { return static_cast(2.32830643653869629E-10 * int32()); } 81 | inline uint8_t int8() { return static_cast(int32()); } 82 | inline bool bit() { return static_cast(int32() & (1 << 0)); } 83 | }; 84 | 85 | 86 | // For accessing the patern data fill must be < length and > 0 87 | // if fill is >= length just output 1's 88 | // if fill is 0 output nothing 89 | struct Bank { 90 | int fill; 91 | int length; 92 | int currentStep; 93 | bool coinFlip; 94 | bool noteOn; 95 | SchmittTrigger jogTrigger; 96 | PulseGenerator gate; 97 | Lcg rng; 98 | 99 | 100 | Bank() { 101 | Reset(); 102 | } 103 | 104 | void Reset() { 105 | fill = 0; 106 | length = 0; 107 | currentStep = 0; 108 | coinFlip = false; 109 | noteOn = false; 110 | gate.pulseTime = 0.0f; 111 | gate.time = 0.0f; 112 | rng.seed = 738; 113 | } 114 | 115 | // Given the current step, fill, length members and the given probablility 116 | // Is the note on or off? 117 | // If on set the gate 118 | void SetNote(const float p, const float glength) { 119 | noteOn = false; 120 | 121 | if (fill > 0) { 122 | // Flip coin 123 | if (p < 0.999f) { 124 | if (rng.flt() <= 1.0f - p) { 125 | coinFlip = true; 126 | } 127 | } 128 | // Normal operations 129 | if (coinFlip == false) { 130 | 131 | if (fill < length) { 132 | const patternBucket pattern_ref(&(bit_pattern_table[((fill * (SEQUENCE_MAX + 1)) + length)])); 133 | if (pattern_ref[currentStep]) { 134 | gate.trigger(glength); 135 | noteOn = true; 136 | } 137 | } else if (coinFlip == false) { // if fill > length output a gate 138 | gate.trigger(glength); 139 | noteOn = true; 140 | } 141 | } 142 | } 143 | } 144 | 145 | void AdvanceStep() { 146 | coinFlip = false; // Make sure we clear any old random note off events 147 | currentStep++; 148 | if (currentStep + 1 > length) { 149 | currentStep = 0; 150 | } 151 | } 152 | 153 | }; 154 | 155 | 156 | 157 | 158 | bool running = true; 159 | SchmittTrigger clockTrigger; // for external clock 160 | SchmittTrigger resetTrigger; // reset button 161 | 162 | Bank bank1; 163 | Bank bank2; 164 | Bank bank3; 165 | Bank bank4; 166 | 167 | double time = 0.0; 168 | double dTime = 1.0 / static_cast(gSampleRate); 169 | int bpm = 120; 170 | double timerLength = 1.0 / (static_cast(bpm) / 60.0); 171 | double timerTime = timerLength; 172 | 173 | // Lights 174 | float gatesLight = 0.0; 175 | 176 | SEQEuclid() { 177 | params.resize(NUM_PARAMS); 178 | inputs.resize(NUM_INPUTS); 179 | outputs.resize(NUM_OUTPUTS); 180 | } 181 | 182 | // Called via menu 183 | void initialize() { 184 | time = 0.0; 185 | dTime = 1.0 / static_cast(gSampleRate); 186 | bpm = 120; 187 | timerLength = 1.0 / (static_cast(bpm) / 60.0); 188 | timerTime = timerLength; 189 | 190 | bank1.Reset(); 191 | bank2.Reset(); 192 | bank3.Reset(); 193 | bank4.Reset(); 194 | } 195 | 196 | //Todo 197 | void randomize() { 198 | } 199 | 200 | void step(); 201 | }; 202 | 203 | 204 | void SEQEuclid::step() { 205 | const float lightLambda = 0.075; 206 | bool nextStep = false; 207 | 208 | // Do clock stuff 209 | if (running) { 210 | time += dTime; 211 | 212 | if (inputs[EXT_CLOCK_INPUT].active) { 213 | 214 | if (clockTrigger.process(inputs[EXT_CLOCK_INPUT].value)) { 215 | nextStep = true; 216 | } 217 | 218 | } else { 219 | 220 | timerLength = 1.0 / (static_cast(bpm) / 60.0); 221 | timerTime -= dTime; 222 | 223 | if (dTime > timerTime) { 224 | timerTime = 0.0; 225 | } 226 | 227 | if (timerTime <= 0.0) { 228 | nextStep = true; 229 | timerTime = timerLength; 230 | } 231 | 232 | } 233 | 234 | bank1.gate.process(dTime); 235 | bank2.gate.process(dTime); 236 | bank3.gate.process(dTime); 237 | bank4.gate.process(dTime); 238 | } 239 | 240 | 241 | 242 | // Deal with inputs and button presses 243 | 244 | // BPM param 245 | bpm = floor(params[BPM_PARAM].value); 246 | 247 | // Reset inputs 248 | if (resetTrigger.process(params[RESET_BUTTON].value + inputs[RESET_INPUT].value)) { 249 | bank1.currentStep = 0; 250 | bank1.gate.pulseTime = 0.0f; 251 | bank1.gate.time = 0.0f; 252 | 253 | bank2.currentStep = 0; 254 | bank2.gate.pulseTime = 0.0f; 255 | bank2.gate.time = 0.0f; 256 | 257 | bank3.currentStep = 0; 258 | bank3.gate.pulseTime = 0.0f; 259 | bank3.gate.time = 0.0f; 260 | 261 | bank4.currentStep = 0; 262 | bank4.gate.pulseTime = 0.0f; 263 | bank4.gate.time = 0.0f; 264 | } 265 | 266 | // Bank controls 267 | 268 | // Fill and length 269 | bank1.fill = floor(params[FILL1_PARAM].value); 270 | bank1.length = floor(params[LENGTH1_PARAM].value); 271 | 272 | bank2.fill = floor(params[FILL2_PARAM].value); 273 | bank2.length = floor(params[LENGTH2_PARAM].value); 274 | 275 | bank3.fill = floor(params[FILL3_PARAM].value); 276 | bank3.length = floor(params[LENGTH3_PARAM].value); 277 | 278 | bank4.fill = floor(params[FILL4_PARAM].value); 279 | bank4.length = floor(params[LENGTH4_PARAM].value); 280 | 281 | // Jog button 282 | if (bank1.jogTrigger.process(params[JOG1_BUTTON].value)) { 283 | bank1.AdvanceStep(); 284 | } 285 | if (bank2.jogTrigger.process(params[JOG2_BUTTON].value)) { 286 | bank2.AdvanceStep(); 287 | } 288 | if (bank3.jogTrigger.process(params[JOG3_BUTTON].value)) { 289 | bank3.AdvanceStep(); 290 | } 291 | if (bank4.jogTrigger.process(params[JOG4_BUTTON].value)) { 292 | bank4.AdvanceStep(); 293 | } 294 | 295 | // Advance step 296 | if (nextStep) { 297 | bank1.AdvanceStep(); 298 | bank2.AdvanceStep(); 299 | bank3.AdvanceStep(); 300 | bank4.AdvanceStep(); 301 | } 302 | 303 | // Generate output 304 | 305 | // See if our notes are on this step 306 | if (nextStep) { 307 | bank1.SetNote(params[PROB1_PARAM].value, 308 | timerLength * params[GATE_LENGTH_PARAM].value); 309 | bank2.SetNote(params[PROB2_PARAM].value, 310 | timerLength * params[GATE_LENGTH_PARAM].value); 311 | bank3.SetNote(params[PROB3_PARAM].value, 312 | timerLength * params[GATE_LENGTH_PARAM].value); 313 | bank4.SetNote(params[PROB4_PARAM].value, 314 | timerLength * params[GATE_LENGTH_PARAM].value); 315 | } 316 | 317 | // Set output high if there's a note currently latched on 318 | // gate.process(0.0f) to get the current state without advancing time 319 | const float gate1 = (bank1.gate.process(0.0f)) ? 10.0f : 0.0f; 320 | const float gate2 = (bank2.gate.process(0.0f)) ? 10.0f : 0.0f; 321 | const float gate3 = (bank3.gate.process(0.0f)) ? 10.0f : 0.0f; 322 | const float gate4 = (bank4.gate.process(0.0f)) ? 10.0f : 0.0f; 323 | 324 | // blast out a trigger for new events 325 | const float trigger1 = (bank1.noteOn && nextStep) ? 10.0f : 0.0f; 326 | const float trigger2 = (bank2.noteOn && nextStep) ? 10.0f : 0.0f; 327 | const float trigger3 = (bank3.noteOn && nextStep) ? 10.0f : 0.0f; 328 | const float trigger4 = (bank4.noteOn && nextStep) ? 10.0f : 0.0f; 329 | 330 | // Setup summed outputs 331 | const float gateOr = (gate1 || gate2 || gate3 || gate4) ? 10.0f : 0.0f; 332 | const float triggerOr = (trigger1 || trigger2 || trigger3 || trigger4) ? 10.0f : 0.0f; 333 | 334 | // Send outputs out 335 | gatesLight = (gateOr >= 1.0f) ? 1.0 : 0.0; 336 | 337 | outputs[GATE1_OUTPUT].value = gate1; 338 | outputs[GATE2_OUTPUT].value = gate2; 339 | outputs[GATE3_OUTPUT].value = gate3; 340 | outputs[GATE4_OUTPUT].value = gate4; 341 | 342 | outputs[TRIGGER1_OUTPUT].value = trigger1; 343 | outputs[TRIGGER2_OUTPUT].value = trigger2; 344 | outputs[TRIGGER3_OUTPUT].value = trigger3; 345 | outputs[TRIGGER4_OUTPUT].value = trigger4; 346 | 347 | outputs[GATE_OR_OUTPUT].value = gateOr; 348 | outputs[TRIGGER_OR_OUTPUT].value = triggerOr; 349 | 350 | } 351 | 352 | 353 | struct SEQEuclidDisplay : TransparentWidget { 354 | int *value; 355 | std::shared_ptr font; 356 | 357 | SEQEuclidDisplay() { 358 | font = Font::load(FONT_FILE); 359 | } 360 | 361 | void draw(NVGcontext *vg) { 362 | // Background 363 | NVGcolor backgroundColor = nvgRGB(0x74, 0x44, 0x44); 364 | NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10); 365 | nvgBeginPath(vg); 366 | nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 5.0); 367 | nvgFillColor(vg, backgroundColor); 368 | nvgFill(vg); 369 | nvgStrokeWidth(vg, 1.0); 370 | nvgStrokeColor(vg, borderColor); 371 | nvgStroke(vg); 372 | 373 | nvgFontSize(vg, 36); 374 | nvgFontFaceId(vg, font->handle); 375 | nvgTextLetterSpacing(vg, 2.5); 376 | 377 | std::string to_display = std::to_string(*value); 378 | Vec textPos = Vec(7.0f, 35.0f); 379 | 380 | NVGcolor textColor = nvgRGB(0xdf, 0xd2, 0x2c); 381 | nvgFillColor(vg, nvgTransRGBA(textColor, 16)); 382 | nvgText(vg, textPos.x, textPos.y, "~~~", NULL); 383 | 384 | textColor = nvgRGB(0xda, 0xe9, 0x29); 385 | nvgFillColor(vg, nvgTransRGBA(textColor, 16)); 386 | nvgText(vg, textPos.x, textPos.y, "\\\\\\", NULL); 387 | 388 | textColor = nvgRGB(0xf0, 0x00, 0x00); 389 | nvgFillColor(vg, textColor); 390 | nvgText(vg, textPos.x, textPos.y, to_display.c_str(), NULL); 391 | } 392 | }; 393 | 394 | 395 | SEQEuclidWidget::SEQEuclidWidget() { 396 | SEQEuclid *module = new SEQEuclid(); 397 | setModule(module); 398 | box.size = Vec(17*22, 380); 399 | 400 | const float bankX[10] = { 8, 94, 134, 220, 258, 296, 324, 351 }; 401 | const float bankY[7] = { 23, 72, 110, 164, 218, 272, 326 }; 402 | 403 | { 404 | Panel *panel = new LightPanel(); 405 | panel->backgroundImage = Image::load(BG_IMAGE_FILE); 406 | panel->box.size = box.size; 407 | addChild(panel); 408 | } 409 | 410 | // bpm display + control 411 | 412 | { 413 | SEQEuclidDisplay *display = new SEQEuclidDisplay(); 414 | display->box.pos = Vec(bankX[0], bankY[0]); 415 | display->box.size = Vec(82, 42); 416 | display->value = &module->bpm; 417 | addChild(display); 418 | } 419 | addParam(createParam(Vec(bankX[1], bankY[0]+3), module, SEQEuclid::BPM_PARAM, 30.0, 256.0, 120.0)); 420 | 421 | 422 | // Next row of stuff 423 | 424 | addInput(createInput(Vec(bankX[0], bankY[1]), module, SEQEuclid::EXT_CLOCK_INPUT)); 425 | addInput(createInput(Vec(bankX[1], bankY[1]), module, SEQEuclid::RESET_INPUT)); 426 | addParam(createParam(Vec(bankX[1]+24+4, bankY[1] + 4), module, SEQEuclid::RESET_BUTTON, 0.0, 1.0, 0.0)); 427 | addParam(createParam(Vec(bankX[5]-6, bankY[1] + 3), module, SEQEuclid::GATE_LENGTH_PARAM, 0.0, 1.0, 1.0)); 428 | 429 | // Row displays 430 | 431 | { 432 | SEQEuclidDisplay *display = new SEQEuclidDisplay(); 433 | display->box.pos = Vec(bankX[0], bankY[2]); 434 | display->box.size = Vec(82, 42); 435 | display->value = &module->bank1.fill; 436 | addChild(display); 437 | } 438 | { 439 | SEQEuclidDisplay *display = new SEQEuclidDisplay(); 440 | display->box.pos = Vec(bankX[2], bankY[2]); 441 | display->box.size = Vec(82, 42); 442 | display->value = &module->bank1.length; 443 | addChild(display); 444 | } 445 | 446 | { 447 | SEQEuclidDisplay *display = new SEQEuclidDisplay(); 448 | display->box.pos = Vec(bankX[0], bankY[3]); 449 | display->box.size = Vec(82, 42); 450 | display->value = &module->bank2.fill; 451 | addChild(display); 452 | } 453 | { 454 | SEQEuclidDisplay *display = new SEQEuclidDisplay(); 455 | display->box.pos = Vec(bankX[2], bankY[3]); 456 | display->box.size = Vec(82, 42); 457 | display->value = &module->bank2.length; 458 | addChild(display); 459 | } 460 | { 461 | SEQEuclidDisplay *display = new SEQEuclidDisplay(); 462 | display->box.pos = Vec(bankX[0], bankY[4]); 463 | display->box.size = Vec(82, 42); 464 | display->value = &module->bank3.fill; 465 | addChild(display); 466 | } 467 | { 468 | SEQEuclidDisplay *display = new SEQEuclidDisplay(); 469 | display->box.pos = Vec(bankX[2], bankY[4]); 470 | display->box.size = Vec(82, 42); 471 | display->value = &module->bank3.length; 472 | addChild(display); 473 | } 474 | { 475 | SEQEuclidDisplay *display = new SEQEuclidDisplay(); 476 | display->box.pos = Vec(bankX[0], bankY[5]); 477 | display->box.size = Vec(82, 42); 478 | display->value = &module->bank4.fill; 479 | addChild(display); 480 | } 481 | { 482 | SEQEuclidDisplay *display = new SEQEuclidDisplay(); 483 | display->box.pos = Vec(bankX[2], bankY[5]); 484 | display->box.size = Vec(82, 42); 485 | display->value = &module->bank4.length; 486 | addChild(display); 487 | } 488 | 489 | 490 | // Rows of bank controlls 491 | 492 | for (int row = 0; row < 4; row++) { 493 | addParam(createParam(Vec(bankX[1], bankY[row + 2] + 3), module, SEQEuclid::FILL1_PARAM+row, 0.0, 256.0, 0.0)); 494 | addParam(createParam(Vec(bankX[3], bankY[row + 2] + 3), module, SEQEuclid::LENGTH1_PARAM+row, 0.0, 256.0, 0.0)); 495 | addParam(createParam(Vec(bankX[4], bankY[row + 2] + 3), module, SEQEuclid::PROB1_PARAM + row, 0.0, 1.0, 1.0)); 496 | addOutput(createOutput(Vec(bankX[5], bankY[row + 2] + 9), module, SEQEuclid::GATE1_OUTPUT + row)); 497 | addOutput(createOutput(Vec(bankX[6], bankY[row + 2] + 9), module, SEQEuclid::TRIGGER1_OUTPUT + row)); 498 | addParam(createParam(Vec(bankX[7], bankY[row + 2] + 13), module, SEQEuclid::JOG1_BUTTON + row, 0.0, 1.0, 0.0)); 499 | } 500 | 501 | // Final 2 outputs and output light 502 | 503 | addOutput(createOutput(Vec(bankX[5], bankY[6] + 8), module, SEQEuclid::GATE_OR_OUTPUT)); 504 | addOutput(createOutput(Vec(bankX[6], bankY[6] + 8), module, SEQEuclid::TRIGGER_OR_OUTPUT)); 505 | addChild(createValueLight>(Vec(bankX[7]+4, bankY[6] + 16), &module->gatesLight)); 506 | 507 | // Make sure it stays put 508 | 509 | addChild(createScrew(Vec(15, 0))); 510 | addChild(createScrew(Vec(box.size.x-30, 0))); 511 | addChild(createScrew(Vec(15, 365))); 512 | addChild(createScrew(Vec(box.size.x-30, 365))); 513 | 514 | } 515 | --------------------------------------------------------------------------------