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.

300 lines
7.6KB

  1. #include "AH.hpp"
  2. #include "Core.hpp"
  3. #include "UI.hpp"
  4. #include "dsp/digital.hpp"
  5. #include <iostream>
  6. namespace rack_plugin_AmalgamatedHarmonics {
  7. struct Circle : AHModule {
  8. const static int NUM_PITCHES = 6;
  9. enum ParamIds {
  10. KEY_PARAM,
  11. MODE_PARAM,
  12. NUM_PARAMS
  13. };
  14. enum InputIds {
  15. ROTL_INPUT,
  16. ROTR_INPUT,
  17. KEY_INPUT,
  18. MODE_INPUT,
  19. NUM_INPUTS
  20. };
  21. enum OutputIds {
  22. KEY_OUTPUT,
  23. MODE_OUTPUT,
  24. NUM_OUTPUTS
  25. };
  26. enum LightIds {
  27. ENUMS(MODE_LIGHT,7),
  28. ENUMS(BKEY_LIGHT,12),
  29. ENUMS(CKEY_LIGHT,12),
  30. NUM_LIGHTS
  31. };
  32. enum Scaling {
  33. CHROMATIC,
  34. FIFTHS
  35. };
  36. Scaling voltScale = FIFTHS;
  37. Circle() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  38. void step() override;
  39. json_t *toJson() override {
  40. json_t *rootJ = json_object();
  41. // gateMode
  42. json_t *scaleModeJ = json_integer((int) voltScale);
  43. json_object_set_new(rootJ, "scale", scaleModeJ);
  44. return rootJ;
  45. }
  46. void fromJson(json_t *rootJ) override {
  47. // gateMode
  48. json_t *scaleModeJ = json_object_get(rootJ, "scale");
  49. if (scaleModeJ) {
  50. voltScale = (Scaling)json_integer_value(scaleModeJ);
  51. }
  52. }
  53. SchmittTrigger rotLTrigger;
  54. SchmittTrigger rotRTrigger;
  55. PulseGenerator stepPulse;
  56. int baseKeyIndex = 0;
  57. int curKeyIndex = 0;
  58. int curMode = 0;
  59. int poll = 50000;
  60. };
  61. void Circle::step() {
  62. AHModule::step();
  63. // Get inputs from Rack
  64. float rotLInput = inputs[ROTL_INPUT].value;
  65. float rotRInput = inputs[ROTR_INPUT].value;
  66. int newKeyIndex = 0;
  67. int deg;
  68. if (inputs[KEY_INPUT].active) {
  69. float fRoot = inputs[KEY_INPUT].value;
  70. if (voltScale == FIFTHS) {
  71. newKeyIndex = CoreUtil().getKeyFromVolts(fRoot);
  72. } else {
  73. CoreUtil().getPitchFromVolts(fRoot, Core::NOTE_C, Core::SCALE_CHROMATIC, &newKeyIndex, &deg);
  74. }
  75. } else {
  76. newKeyIndex = params[KEY_PARAM].value;
  77. }
  78. int newMode = 0;
  79. if (inputs[MODE_INPUT].active) {
  80. float fMode = inputs[MODE_INPUT].value;
  81. newMode = round(rescale(fabs(fMode), 0.0f, 10.0f, 0.0f, 6.0f));
  82. } else {
  83. newMode = params[MODE_PARAM].value;
  84. }
  85. curMode = newMode;
  86. // Process inputs
  87. bool rotLStatus = rotLTrigger.process(rotLInput);
  88. bool rotRStatus = rotRTrigger.process(rotRInput);
  89. if (rotLStatus) {
  90. if (debugEnabled()) { std::cout << stepX << " Rotate left: " << curKeyIndex; }
  91. if (voltScale == FIFTHS) {
  92. if (curKeyIndex == 0) {
  93. curKeyIndex = 11;
  94. } else {
  95. curKeyIndex--;
  96. }
  97. } else {
  98. curKeyIndex = curKeyIndex + 5;
  99. if (curKeyIndex > 11) {
  100. curKeyIndex = curKeyIndex - 12;
  101. }
  102. }
  103. if (debugEnabled()) { std::cout << " -> " << curKeyIndex << std::endl; }
  104. }
  105. if (rotRStatus) {
  106. if (debugEnabled()) { std::cout << stepX << " Rotate right: " << curKeyIndex; }
  107. if (voltScale == FIFTHS) {
  108. if (curKeyIndex == 11) {
  109. curKeyIndex = 0;
  110. } else {
  111. curKeyIndex++;
  112. }
  113. } else {
  114. curKeyIndex = curKeyIndex - 5;
  115. if (curKeyIndex < 0) {
  116. curKeyIndex = curKeyIndex + 12;
  117. }
  118. }
  119. if (debugEnabled()) { std::cout << " -> " << curKeyIndex << std::endl; }
  120. }
  121. if (rotLStatus && rotRStatus) {
  122. if (debugEnabled()) { std::cout << stepX << " Reset " << curKeyIndex << std::endl; }
  123. curKeyIndex = baseKeyIndex;
  124. }
  125. if (newKeyIndex != baseKeyIndex) {
  126. if (debugEnabled()) { std::cout << stepX << " New base: " << newKeyIndex << std::endl;}
  127. baseKeyIndex = newKeyIndex;
  128. curKeyIndex = newKeyIndex;
  129. }
  130. int curKey;
  131. int baseKey;
  132. if (voltScale == FIFTHS) {
  133. curKey = CoreUtil().CIRCLE_FIFTHS[curKeyIndex];
  134. baseKey = CoreUtil().CIRCLE_FIFTHS[baseKeyIndex];
  135. } else {
  136. curKey = curKeyIndex;
  137. baseKey = baseKeyIndex;
  138. }
  139. float keyVolts = CoreUtil().getVoltsFromKey(curKey);
  140. float modeVolts = CoreUtil().getVoltsFromMode(curMode);
  141. for (int i = 0; i < Core::NUM_NOTES; i++) {
  142. lights[CKEY_LIGHT + i].value = 0.0;
  143. lights[BKEY_LIGHT + i].value = 0.0;
  144. }
  145. lights[CKEY_LIGHT + curKey].value = 1.0;
  146. lights[BKEY_LIGHT + baseKey].value = 1.0;
  147. for (int i = 0; i < Core::NUM_MODES; i++) {
  148. lights[MODE_LIGHT + i].value = 0.0;
  149. }
  150. lights[MODE_LIGHT + curMode].value = 1.0;
  151. outputs[KEY_OUTPUT].value = keyVolts;
  152. outputs[MODE_OUTPUT].value = modeVolts;
  153. }
  154. struct CircleWidget : ModuleWidget {
  155. CircleWidget(Circle *module);
  156. Menu *createContextMenu() override;
  157. };
  158. CircleWidget::CircleWidget(Circle *module) : ModuleWidget(module) {
  159. UI ui;
  160. box.size = Vec(240, 380);
  161. {
  162. SVGPanel *panel = new SVGPanel();
  163. panel->box.size = box.size;
  164. panel->setBackground(SVG::load(assetPlugin(plugin, "res/Circle.svg")));
  165. addChild(panel);
  166. }
  167. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 0, true, false), Port::INPUT, module, Circle::ROTL_INPUT));
  168. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, 0, true, false), Port::INPUT, module, Circle::ROTR_INPUT));
  169. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 5, true, false), Port::INPUT, module, Circle::KEY_INPUT));
  170. addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 1, 5, true, false), module, Circle::KEY_PARAM, 0.0, 11.0, 0.0));
  171. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 2, 5, true, false), Port::INPUT, module, Circle::MODE_INPUT));
  172. addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 3, 5, true, false), module, Circle::MODE_PARAM, 0.0, 6.0, 0.0));
  173. float div = (M_PI * 2) / 12.0f;
  174. for (int i = 0; i < 12; i++) {
  175. float cosDiv = cos(div * i);
  176. float sinDiv = sin(div * i);
  177. float xPos = sinDiv * 52.5;
  178. float yPos = cosDiv * 52.5;
  179. float xxPos = sinDiv * 60.0;
  180. float yyPos = cosDiv * 60.0;
  181. // ui.calculateKeyboard(i, xSpace, xOffset, 230.0, &xPos, &yPos, &scale);
  182. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xxPos + 116.5, 149.5 - yyPos), module, Circle::CKEY_LIGHT + CoreUtil().CIRCLE_FIFTHS[i]));
  183. // ui.calculateKeyboard(i, xSpace, xOffset + 72.0, 165.0, &xPos, &yPos, &scale);
  184. addChild(ModuleLightWidget::create<SmallLight<RedLight>>(Vec(xPos + 116.5, 149.5 - yPos), module, Circle::BKEY_LIGHT + CoreUtil().CIRCLE_FIFTHS[i]));
  185. }
  186. float xOffset = 18.0;
  187. for (int i = 0; i < 7; i++) {
  188. float xPos = 2 * xOffset + i * 18.2;
  189. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xPos, 280.0), module, Circle::MODE_LIGHT + i));
  190. }
  191. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 4, 5, true, false), Port::OUTPUT, module, Circle::KEY_OUTPUT));
  192. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, 5, true, false), Port::OUTPUT, module, Circle::MODE_OUTPUT));
  193. }
  194. struct CircleScalingItem : MenuItem {
  195. Circle *circle;
  196. Circle::Scaling scalingMode;
  197. void onAction(EventAction &e) override {
  198. circle->voltScale = scalingMode;
  199. }
  200. void step() override {
  201. rightText = (circle->voltScale == scalingMode) ? "✔" : "";
  202. }
  203. };
  204. Menu *CircleWidget::createContextMenu() {
  205. Menu *menu = ModuleWidget::createContextMenu();
  206. MenuLabel *spacerLabel = new MenuLabel();
  207. menu->addChild(spacerLabel);
  208. Circle *circle = dynamic_cast<Circle*>(module);
  209. assert(circle);
  210. MenuLabel *modeLabel = new MenuLabel();
  211. modeLabel->text = "Root Volt Scaling";
  212. menu->addChild(modeLabel);
  213. CircleScalingItem *fifthsItem = new CircleScalingItem();
  214. fifthsItem->text = "Fifths";
  215. fifthsItem->circle = circle;
  216. fifthsItem->scalingMode = Circle::FIFTHS;
  217. menu->addChild(fifthsItem);
  218. CircleScalingItem *chromaticItem = new CircleScalingItem();
  219. chromaticItem->text = "Chromatic (V/OCT)";
  220. chromaticItem->circle = circle;
  221. chromaticItem->scalingMode = Circle::CHROMATIC;
  222. menu->addChild(chromaticItem);
  223. return menu;
  224. }
  225. } // namespace rack_plugin_AmalgamatedHarmonics
  226. using namespace rack_plugin_AmalgamatedHarmonics;
  227. RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Circle) {
  228. Model *modelCircle = Model::create<Circle, CircleWidget>( "Amalgamated Harmonics", "Circle", "Fifths and Fourths", SEQUENCER_TAG);
  229. return modelCircle;
  230. }
  231. // ♯♭