You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

367 lines
13KB

  1. //***********************************************************************************************
  2. //Chain-able keyboard module for VCV Rack by Marc Boulé
  3. //
  4. //Based on code from the Fundamental and Audible Instruments plugins by Andrew Belt and graphics
  5. // from the Component Library by Wes Milholen.
  6. //See ./LICENSE.txt for all licenses
  7. //See ./res/fonts/ for font licenses
  8. //
  9. //Module inspired by:
  10. // * the Autodafe keyboard by Antonio Grazioli
  11. // * the cf mixer by Clément Foulc
  12. // * Twisted Electrons' KeyChain
  13. //
  14. //***********************************************************************************************
  15. #include "ImpromptuModular.hpp"
  16. namespace rack_plugin_ImpromptuModular {
  17. struct TwelveKey : Module {
  18. enum ParamIds {
  19. OCTINC_PARAM,
  20. OCTDEC_PARAM,
  21. ENUMS(KEY_PARAMS, 12),
  22. NUM_PARAMS
  23. };
  24. enum InputIds {
  25. GATE_INPUT,
  26. CV_INPUT,
  27. OCT_INPUT,
  28. NUM_INPUTS
  29. };
  30. enum OutputIds {
  31. GATE_OUTPUT,
  32. CV_OUTPUT,
  33. OCT_OUTPUT,
  34. NUM_OUTPUTS
  35. };
  36. enum LightIds {
  37. PRESS_LIGHT,// no longer used
  38. ENUMS(KEY_LIGHTS, 12),
  39. NUM_LIGHTS
  40. };
  41. // Need to save
  42. int panelTheme = 0;
  43. int octaveNum;// 0 to 9
  44. float cv;
  45. bool stateInternal;// false when pass through CV and Gate, true when CV and gate from this module
  46. // No need to save
  47. //float gateLight = 0.0f;
  48. unsigned long noteLightCounter;// 0 when no key to light, downward step counter timer when key lit
  49. int lastKeyPressed;// 0 to 11
  50. int lightRefreshCounter;
  51. SchmittTrigger keyTriggers[12];
  52. SchmittTrigger gateInputTrigger;
  53. SchmittTrigger octIncTrigger;
  54. SchmittTrigger octDecTrigger;
  55. TwelveKey() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  56. onReset();
  57. }
  58. void onReset() override {
  59. octaveNum = 4;
  60. cv = 0.0f;
  61. stateInternal = inputs[GATE_INPUT].active ? false : true;
  62. noteLightCounter = 0ul;
  63. lastKeyPressed = 0;
  64. lightRefreshCounter = 0;
  65. }
  66. void onRandomize() override {
  67. octaveNum = randomu32() % 10;
  68. cv = ((float)(octaveNum - 4)) + ((float)(randomu32() % 12)) / 12.0f;
  69. stateInternal = inputs[GATE_INPUT].active ? false : true;
  70. noteLightCounter = 0ul;
  71. lastKeyPressed = 0;
  72. }
  73. json_t *toJson() override {
  74. json_t *rootJ = json_object();
  75. // panelTheme
  76. json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme));
  77. // cv
  78. json_object_set_new(rootJ, "cv", json_real(cv));
  79. // octave
  80. json_object_set_new(rootJ, "octave", json_integer(octaveNum));
  81. // stateInternal
  82. json_object_set_new(rootJ, "stateInternal", json_boolean(stateInternal));
  83. return rootJ;
  84. }
  85. void fromJson(json_t *rootJ) override {
  86. // panelTheme
  87. json_t *panelThemeJ = json_object_get(rootJ, "panelTheme");
  88. if (panelThemeJ)
  89. panelTheme = json_integer_value(panelThemeJ);
  90. // cv
  91. json_t *cvJ = json_object_get(rootJ, "cv");
  92. if (cvJ)
  93. cv = json_real_value(cvJ);
  94. // octave
  95. json_t *octaveJ = json_object_get(rootJ, "octave");
  96. if (octaveJ)
  97. octaveNum = json_integer_value(octaveJ);
  98. // stateInternal
  99. json_t *stateInternalJ = json_object_get(rootJ, "stateInternal");
  100. if (stateInternalJ)
  101. stateInternal = json_is_true(stateInternalJ);
  102. }
  103. // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate()
  104. void step() override {
  105. static const float noteLightTime = 0.5f;// seconds
  106. //********** Buttons, knobs, switches and inputs **********
  107. // Octave buttons and input
  108. if (octIncTrigger.process(params[OCTINC_PARAM].value))
  109. octaveNum++;
  110. if (octDecTrigger.process(params[OCTDEC_PARAM].value))
  111. octaveNum--;
  112. if (inputs[OCT_INPUT].active)
  113. octaveNum = ((int) floor(inputs[OCT_INPUT].value));
  114. if (octaveNum > 9) octaveNum = 9;
  115. if (octaveNum < 0) octaveNum = 0;
  116. // Keyboard buttons and gate input
  117. for (int i = 0; i < 12; i++) {
  118. if (keyTriggers[i].process(params[KEY_PARAMS + i].value)) {
  119. cv = ((float)(octaveNum - 4)) + ((float) i) / 12.0f;
  120. stateInternal = true;
  121. noteLightCounter = (unsigned long) (noteLightTime * engineGetSampleRate() / displayRefreshStepSkips);
  122. lastKeyPressed = i;
  123. }
  124. }
  125. if (gateInputTrigger.process(inputs[GATE_INPUT].value)) {
  126. cv = inputs[CV_INPUT].value;
  127. stateInternal = false;
  128. }
  129. //********** Outputs and lights **********
  130. // Gate light (with fade)
  131. int pressed = 0;
  132. for (int i = 0; i < 12; i++)
  133. if (params[KEY_PARAMS + i].value > 0.5f)
  134. pressed++;
  135. /*if (pressed != 0)
  136. gateLight = 1.0f;
  137. else
  138. gateLight -= (gateLight / lightLambda) * engineGetSampleTime();
  139. lights[PRESS_LIGHT].value = gateLight;*/
  140. // cv output
  141. outputs[CV_OUTPUT].value = cv;
  142. // gate output
  143. if (stateInternal == false) {// if receiving a key from left chain
  144. outputs[GATE_OUTPUT].value = inputs[GATE_INPUT].value;
  145. }
  146. else {// key from this
  147. outputs[GATE_OUTPUT].value = (pressed != 0 ? 10.0f : 0.0f);
  148. }
  149. // Octave output
  150. outputs[OCT_OUTPUT].value = round( (float)(octaveNum + 1) );
  151. lightRefreshCounter++;
  152. if (lightRefreshCounter > displayRefreshStepSkips) {
  153. lightRefreshCounter = 0;
  154. // Key lights
  155. for (int i = 0; i < 12; i++)
  156. lights[KEY_LIGHTS + i].value = (( i == lastKeyPressed && (noteLightCounter > 0ul || params[KEY_PARAMS + i].value > 0.5f)) ? 1.0f : 0.0f);
  157. if (noteLightCounter > 0ul)
  158. noteLightCounter--;
  159. }
  160. }
  161. };
  162. struct TwelveKeyWidget : ModuleWidget {
  163. struct OctaveNumDisplayWidget : TransparentWidget {
  164. int *octaveNum;
  165. std::shared_ptr<Font> font;
  166. OctaveNumDisplayWidget() {
  167. font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf"));
  168. }
  169. void draw(NVGcontext *vg) override {
  170. NVGcolor textColor = prepareDisplay(vg, &box);
  171. nvgFontFaceId(vg, font->handle);
  172. //nvgTextLetterSpacing(vg, 2.5);
  173. Vec textPos = Vec(6, 24);
  174. nvgFillColor(vg, nvgTransRGBA(textColor, 16));
  175. nvgText(vg, textPos.x, textPos.y, "~", NULL);
  176. nvgFillColor(vg, textColor);
  177. char displayStr[2];
  178. displayStr[0] = 0x30 + (char) *octaveNum;
  179. displayStr[1] = 0;
  180. nvgText(vg, textPos.x, textPos.y, displayStr, NULL);
  181. }
  182. };
  183. struct PanelThemeItem : MenuItem {
  184. TwelveKey *module;
  185. int theme;
  186. void onAction(EventAction &e) override {
  187. module->panelTheme = theme;
  188. }
  189. void step() override {
  190. rightText = (module->panelTheme == theme) ? "✔" : "";
  191. }
  192. };
  193. Menu *createContextMenu() override {
  194. Menu *menu = ModuleWidget::createContextMenu();
  195. MenuLabel *spacerLabel = new MenuLabel();
  196. menu->addChild(spacerLabel);
  197. TwelveKey *module = dynamic_cast<TwelveKey*>(this->module);
  198. assert(module);
  199. MenuLabel *themeLabel = new MenuLabel();
  200. themeLabel->text = "Panel Theme";
  201. menu->addChild(themeLabel);
  202. PanelThemeItem *lightItem = new PanelThemeItem();
  203. lightItem->text = lightPanelID;// ImpromptuModular.hpp
  204. lightItem->module = module;
  205. lightItem->theme = 0;
  206. menu->addChild(lightItem);
  207. PanelThemeItem *darkItem = new PanelThemeItem();
  208. darkItem->text = darkPanelID;// ImpromptuModular.hpp
  209. darkItem->module = module;
  210. darkItem->theme = 1;
  211. menu->addChild(darkItem);
  212. return menu;
  213. }
  214. TwelveKeyWidget(TwelveKey *module) : ModuleWidget(module) {
  215. // Main panel from Inkscape
  216. DynamicSVGPanel *panel = new DynamicSVGPanel();
  217. panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/TwelveKey.svg")));
  218. panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/TwelveKey_dark.svg")));
  219. box.size = panel->box.size;
  220. panel->mode = &module->panelTheme;
  221. addChild(panel);
  222. // Screws
  223. addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme));
  224. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 0), &module->panelTheme));
  225. addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme));
  226. addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 365), &module->panelTheme));
  227. // ****** Top portion (keys) ******
  228. static const int offsetKeyLEDx = 12;
  229. static const int offsetKeyLEDy = 41;
  230. // Black keys
  231. addParam(ParamWidget::create<InvisibleKey>(Vec(30, 40), module, TwelveKey::KEY_PARAMS + 1, 0.0, 1.0, 0.0));
  232. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(30+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 1));
  233. addParam(ParamWidget::create<InvisibleKey>(Vec(71, 40), module, TwelveKey::KEY_PARAMS + 3, 0.0, 1.0, 0.0));
  234. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(71+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 3));
  235. addParam(ParamWidget::create<InvisibleKey>(Vec(154, 40), module, TwelveKey::KEY_PARAMS + 6, 0.0, 1.0, 0.0));
  236. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(154+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 6));
  237. addParam(ParamWidget::create<InvisibleKey>(Vec(195, 40), module, TwelveKey::KEY_PARAMS + 8, 0.0, 1.0, 0.0));
  238. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(195+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 8));
  239. addParam(ParamWidget::create<InvisibleKey>(Vec(236, 40), module, TwelveKey::KEY_PARAMS + 10, 0.0, 1.0, 0.0));
  240. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(236+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 10));
  241. // White keys
  242. addParam(ParamWidget::create<InvisibleKey>(Vec(10, 112), module, TwelveKey::KEY_PARAMS + 0, 0.0, 1.0, 0.0));
  243. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(10+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 0));
  244. addParam(ParamWidget::create<InvisibleKey>(Vec(51, 112), module, TwelveKey::KEY_PARAMS + 2, 0.0, 1.0, 0.0));
  245. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(51+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 2));
  246. addParam(ParamWidget::create<InvisibleKey>(Vec(92, 112), module, TwelveKey::KEY_PARAMS + 4, 0.0, 1.0, 0.0));
  247. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(92+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 4));
  248. addParam(ParamWidget::create<InvisibleKey>(Vec(133, 112), module, TwelveKey::KEY_PARAMS + 5, 0.0, 1.0, 0.0));
  249. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(133+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 5));
  250. addParam(ParamWidget::create<InvisibleKey>(Vec(174, 112), module, TwelveKey::KEY_PARAMS + 7, 0.0, 1.0, 0.0));
  251. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(174+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 7));
  252. addParam(ParamWidget::create<InvisibleKey>(Vec(215, 112), module, TwelveKey::KEY_PARAMS + 9, 0.0, 1.0, 0.0));
  253. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(215+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 9));
  254. addParam(ParamWidget::create<InvisibleKey>(Vec(256, 112), module, TwelveKey::KEY_PARAMS + 11, 0.0, 1.0, 0.0));
  255. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(256+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 11));
  256. // ****** Bottom portion ******
  257. // Column rulers (horizontal positions)
  258. static const int columnRulerL = 30;
  259. static const int columnRulerR = box.size.x - 25 - columnRulerL;
  260. static const int columnRulerM = box.size.x / 2 - 14;
  261. // Row rulers (vertical positions)
  262. static const int rowRuler0 = 220;
  263. static const int rowRulerStep = 49;
  264. static const int rowRuler1 = rowRuler0 + rowRulerStep;
  265. static const int rowRuler2 = rowRuler1 + rowRulerStep;
  266. // Left side inputs
  267. addInput(createDynamicPort<IMPort>(Vec(columnRulerL, rowRuler0), Port::INPUT, module, TwelveKey::CV_INPUT, &module->panelTheme));
  268. addInput(createDynamicPort<IMPort>(Vec(columnRulerL, rowRuler1), Port::INPUT, module, TwelveKey::GATE_INPUT, &module->panelTheme));
  269. addInput(createDynamicPort<IMPort>(Vec(columnRulerL, rowRuler2), Port::INPUT, module, TwelveKey::OCT_INPUT, &module->panelTheme));
  270. // Middle
  271. // Press LED (moved other controls below up by 16 px when removed, to better center)
  272. //addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(columnRulerM + offsetMediumLight, rowRuler0 - 31 + offsetMediumLight), module, TwelveKey::PRESS_LIGHT));
  273. // Octave display
  274. OctaveNumDisplayWidget *octaveNumDisplay = new OctaveNumDisplayWidget();
  275. octaveNumDisplay->box.pos = Vec(columnRulerM + 2, rowRuler1 - 27 + vOffsetDisplay);
  276. octaveNumDisplay->box.size = Vec(24, 30);// 1 character
  277. octaveNumDisplay->octaveNum = &module->octaveNum;
  278. addChild(octaveNumDisplay);
  279. // Octave buttons
  280. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRulerM - 20 + offsetCKD6b, rowRuler2 - 26 + offsetCKD6b), module, TwelveKey::OCTDEC_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  281. addParam(createDynamicParam<IMBigPushButton>(Vec(columnRulerM + 22 + offsetCKD6b, rowRuler2 - 26 + offsetCKD6b), module, TwelveKey::OCTINC_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme));
  282. // Right side outputs
  283. addOutput(createDynamicPort<IMPort>(Vec(columnRulerR, rowRuler0), Port::OUTPUT, module, TwelveKey::CV_OUTPUT, &module->panelTheme));
  284. addOutput(createDynamicPort<IMPort>(Vec(columnRulerR, rowRuler1), Port::OUTPUT, module, TwelveKey::GATE_OUTPUT, &module->panelTheme));
  285. addOutput(createDynamicPort<IMPort>(Vec(columnRulerR, rowRuler2), Port::OUTPUT, module, TwelveKey::OCT_OUTPUT, &module->panelTheme));
  286. }
  287. };
  288. } // namespace rack_plugin_ImpromptuModular
  289. using namespace rack_plugin_ImpromptuModular;
  290. RACK_PLUGIN_MODEL_INIT(ImpromptuModular, TwelveKey) {
  291. Model *modelTwelveKey = Model::create<TwelveKey, TwelveKeyWidget>("Impromptu Modular", "Twelve-Key", "CTRL - Twelve-Key", CONTROLLER_TAG);
  292. return modelTwelveKey;
  293. }