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.

230 lines
6.5KB

  1. #include "global_pre.hpp"
  2. #include "Squinky.hpp"
  3. #include "FrequencyShifter.h"
  4. #include "WidgetComposite.h"
  5. #include "ctrl/SqMenuItem.h"
  6. #ifdef _BOOTY
  7. /**
  8. * Implementation class for BootyModule
  9. */
  10. struct BootyModule : Module
  11. {
  12. BootyModule();
  13. /**
  14. * Overrides of Module functions
  15. */
  16. void step() override;
  17. json_t *toJson() override;
  18. void fromJson(json_t *rootJ) override;
  19. void onSampleRateChange() override;
  20. FrequencyShifter<WidgetComposite> shifter;
  21. private:
  22. typedef float T;
  23. public:
  24. ChoiceButton * rangeChoice;
  25. };
  26. extern float values[];
  27. extern const char* ranges[];
  28. BootyModule::BootyModule() : Module(shifter.NUM_PARAMS, shifter.NUM_INPUTS, shifter.NUM_OUTPUTS, shifter.NUM_LIGHTS),
  29. shifter(this)
  30. {
  31. // TODO: can we assume onSampleRateChange() gets called first, so this is unnecessary?
  32. onSampleRateChange();
  33. shifter.init();
  34. }
  35. void BootyModule::onSampleRateChange()
  36. {
  37. T rate = engineGetSampleRate();
  38. shifter.setSampleRate(rate);
  39. }
  40. json_t *BootyModule::toJson()
  41. {
  42. json_t *rootJ = json_object();
  43. const int rg = shifter.freqRange;
  44. json_object_set_new(rootJ, "range", json_integer(rg));
  45. return rootJ;
  46. }
  47. void BootyModule::fromJson(json_t *rootJ)
  48. {
  49. json_t *driverJ = json_object_get(rootJ, "range");
  50. if (driverJ) {
  51. const int rg = json_number_value(driverJ);
  52. // TODO: should we be more robust about float <> int issues?
  53. //need to tell the control what text to display
  54. for (int i = 0; i < 5; ++i) {
  55. if (rg == values[i]) {
  56. rangeChoice->text = ranges[i];
  57. }
  58. }
  59. shifter.freqRange = rg;
  60. }
  61. }
  62. void BootyModule::step()
  63. {
  64. shifter.step();
  65. }
  66. /***********************************************************************************
  67. *
  68. * RangeChoice selector widget
  69. *
  70. ***********************************************************************************/
  71. const char* ranges[5] = {
  72. "5 Hz",
  73. "50 Hz",
  74. "500 Hz",
  75. "5 kHz",
  76. "exp"
  77. };
  78. float values[5] = {
  79. 5,
  80. 50,
  81. 500,
  82. 5000,
  83. 0
  84. };
  85. struct RangeItem : MenuItem
  86. {
  87. RangeItem(int index, float * output, ChoiceButton * inParent) :
  88. rangeIndex(index), rangeOut(output), rangeChoice(inParent)
  89. {
  90. text = ranges[index];
  91. }
  92. const int rangeIndex;
  93. float * const rangeOut;
  94. ChoiceButton* const rangeChoice;
  95. void onAction(EventAction &e) override
  96. {
  97. rangeChoice->text = ranges[rangeIndex];
  98. *rangeOut = values[rangeIndex];
  99. }
  100. };
  101. struct RangeChoice : ChoiceButton
  102. {
  103. RangeChoice(float * out, const Vec& pos, float width) : output(out)
  104. {
  105. assert(*output == 5);
  106. this->text = std::string(ranges[0]);
  107. this->box.pos = pos;
  108. this->box.size.x = width;
  109. }
  110. float * const output;
  111. void onAction(EventAction &e) override
  112. {
  113. Menu *menu = rack::global_ui->ui.gScene->createMenu();
  114. menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round();
  115. menu->box.size.x = box.size.x;
  116. {
  117. menu->addChild(new RangeItem(0, output, this));
  118. menu->addChild(new RangeItem(1, output, this));
  119. menu->addChild(new RangeItem(2, output, this));
  120. menu->addChild(new RangeItem(3, output, this));
  121. menu->addChild(new RangeItem(4, output, this));
  122. }
  123. }
  124. };
  125. ////////////////////
  126. // module widget
  127. ////////////////////
  128. struct BootyWidget : ModuleWidget
  129. {
  130. BootyWidget(BootyModule *);
  131. Menu* createContextMenu() override;
  132. };
  133. /**
  134. * Widget constructor will describe my implementation structure and
  135. * provide meta-data.
  136. * This is not shared by all modules in the DLL, just one
  137. */
  138. BootyWidget::BootyWidget(BootyModule *module) : ModuleWidget(module)
  139. {
  140. box.size = Vec(6 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
  141. {
  142. SVGPanel *panel = new SVGPanel();
  143. panel->box.size = box.size;
  144. panel->setBackground(SVG::load(assetPlugin(plugin, "res/booty_panel.svg")));
  145. addChild(panel);
  146. }
  147. const int leftInputX = 11;
  148. const int rightInputX = 55;
  149. const int row0 = 45;
  150. const int row1 = 102;
  151. static int row2 = 186;
  152. // Inputs on Row 0
  153. addInput(Port::create<PJ301MPort>(Vec(leftInputX, row0), Port::INPUT, module, module->shifter.AUDIO_INPUT));
  154. addInput(Port::create<PJ301MPort>(Vec(rightInputX, row0), Port::INPUT, module, module->shifter.CV_INPUT));
  155. // shift Range on row 2
  156. const float margin = 16;
  157. float xPos = margin;
  158. float width = 6 * RACK_GRID_WIDTH - 2 * margin;
  159. // TODO: why do we need to reach into the module from here? I guess any
  160. // time UI callbacks need to go bak..
  161. module->rangeChoice = new RangeChoice(&module->shifter.freqRange, Vec(xPos, row2), width);
  162. addChild(module->rangeChoice);
  163. // knob on row 1
  164. addParam(ParamWidget::create<Rogan3PSBlue>(Vec(18, row1), module, module->shifter.PITCH_PARAM, -5.0, 5.0, 0.0));
  165. const float row3 = 317.5;
  166. // Outputs on row 3
  167. const float leftOutputX = 9.5;
  168. const float rightOutputX = 55.5;
  169. addOutput(Port::create<PJ301MPort>(Vec(leftOutputX, row3), Port::OUTPUT, module, module->shifter.SIN_OUTPUT));
  170. addOutput(Port::create<PJ301MPort>(Vec(rightOutputX, row3), Port::OUTPUT, module, module->shifter.COS_OUTPUT));
  171. // screws
  172. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  173. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  174. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  175. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  176. }
  177. inline Menu* BootyWidget::createContextMenu()
  178. {
  179. Menu* theMenu = ModuleWidget::createContextMenu();
  180. ManualMenuItem* manual = new ManualMenuItem(
  181. "https://github.com/squinkylabs/SquinkyVCV/blob/master/docs/shifter.md");
  182. theMenu->addChild(manual);
  183. return theMenu;
  184. }
  185. // Specify the Module and ModuleWidget subclass, human-readable
  186. // manufacturer name for categorization, module slug (should never
  187. // change), human-readable module name, and any number of tags
  188. // (found in `include/tags.hpp`) separated by commas.
  189. RACK_PLUGIN_MODEL_INIT(squinkylabs_plug1, Booty) {
  190. Model *modelBootyModule = Model::create<BootyModule, BootyWidget>("Squinky Labs",
  191. "squinkylabs-freqshifter",
  192. "Booty Shifter: Frequency Shifter", EFFECT_TAG, RING_MODULATOR_TAG);
  193. return modelBootyModule;
  194. }
  195. #endif