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.

237 lines
5.8KB

  1. #include <string.h>
  2. #include "JWModules.hpp"
  3. #include "dsp/digital.hpp"
  4. namespace rack_plugin_JW_Modules {
  5. #define BUFFER_SIZE 512
  6. struct MinMax : Module {
  7. enum ParamIds {
  8. TIME_PARAM,
  9. TRIG_PARAM,
  10. NUM_PARAMS
  11. };
  12. enum InputIds {
  13. X_INPUT,
  14. Y_INPUT,
  15. TRIG_INPUT,
  16. NUM_INPUTS
  17. };
  18. enum OutputIds {
  19. NUM_OUTPUTS
  20. };
  21. float bufferX[BUFFER_SIZE] = {};
  22. float bufferY[BUFFER_SIZE] = {};
  23. int bufferIndex = 0;
  24. float frameIndex = 0;
  25. SchmittTrigger sumTrigger;
  26. SchmittTrigger extTrigger;
  27. bool lissajous = false;
  28. SchmittTrigger resetTrigger;
  29. MinMax() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
  30. void step() override;
  31. json_t *toJson() override {
  32. json_t *rootJ = json_object();
  33. json_object_set_new(rootJ, "lissajous", json_integer((int) lissajous));
  34. return rootJ;
  35. }
  36. void fromJson(json_t *rootJ) override {
  37. json_t *sumJ = json_object_get(rootJ, "lissajous");
  38. if (sumJ)
  39. lissajous = json_integer_value(sumJ);
  40. }
  41. void reset() override {
  42. lissajous = false;
  43. }
  44. };
  45. void MinMax::step() {
  46. // Compute time
  47. float deltaTime = powf(2.0, params[TIME_PARAM].value);
  48. int frameCount = (int)ceilf(deltaTime * engineGetSampleRate());
  49. // Add frame to buffer
  50. if (bufferIndex < BUFFER_SIZE) {
  51. if (++frameIndex > frameCount) {
  52. frameIndex = 0;
  53. bufferX[bufferIndex] = inputs[X_INPUT].value;
  54. bufferY[bufferIndex] = inputs[Y_INPUT].value;
  55. bufferIndex++;
  56. }
  57. }
  58. // Are we waiting on the next trigger?
  59. if (bufferIndex >= BUFFER_SIZE) {
  60. // Trigger immediately if external but nothing plugged in, or in Lissajous mode
  61. if (lissajous) {
  62. bufferIndex = 0;
  63. frameIndex = 0;
  64. return;
  65. }
  66. // Reset the Schmitt trigger so we don't trigger immediately if the input is high
  67. if (frameIndex == 0) {
  68. resetTrigger.reset();
  69. }
  70. frameIndex++;
  71. // Must go below 0.1V to trigger
  72. // resetTrigger.setThresholds(params[TRIG_PARAM].value - 0.1, params[TRIG_PARAM].value);
  73. float gate = inputs[X_INPUT].value;
  74. // Reset if triggered
  75. float holdTime = 0.1;
  76. if (resetTrigger.process(gate) || (frameIndex >= engineGetSampleRate() * holdTime)) {
  77. bufferIndex = 0; frameIndex = 0; return;
  78. }
  79. // Reset if we've waited too long
  80. if (frameIndex >= engineGetSampleRate() * holdTime) {
  81. bufferIndex = 0; frameIndex = 0; return;
  82. }
  83. }
  84. }
  85. struct MinMaxDisplay : TransparentWidget {
  86. MinMax *module;
  87. int frame = 0;
  88. std::shared_ptr<Font> font;
  89. struct Stats {
  90. float vrms, vpp, vmin, vmax;
  91. void calculate(float *values) {
  92. vrms = 0.0;
  93. vmax = -INFINITY;
  94. vmin = INFINITY;
  95. for (int i = 0; i < BUFFER_SIZE; i++) {
  96. float v = values[i];
  97. vrms += v*v;
  98. vmax = fmaxf(vmax, v);
  99. vmin = fminf(vmin, v);
  100. }
  101. vrms = sqrtf(vrms / BUFFER_SIZE);
  102. vpp = vmax - vmin;
  103. }
  104. };
  105. Stats statsX, statsY;
  106. MinMaxDisplay() {
  107. font = Font::load(assetPlugin(plugin, "res/DejaVuSansMono.ttf"));
  108. }
  109. void drawStats(NVGcontext *vg, Vec pos, const char *title, Stats *stats) {
  110. nvgFontSize(vg, 24);
  111. nvgFontFaceId(vg, font->handle);
  112. nvgTextLetterSpacing(vg, -2);
  113. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x80));
  114. char text[128];
  115. snprintf(text, sizeof(text), "%5.2f", stats->vmin);
  116. nvgText(vg, pos.x + 10, pos.y + 28, text, NULL);
  117. snprintf(text, sizeof(text), "%5.2f", stats->vmax);
  118. nvgText(vg, pos.x + 10, pos.y + 78, text, NULL);
  119. }
  120. void draw(NVGcontext *vg) {
  121. float gainX = 1;
  122. float gainY = 1;
  123. float offsetX = 0;
  124. float offsetY = 0;
  125. float valuesX[BUFFER_SIZE];
  126. float valuesY[BUFFER_SIZE];
  127. for (int i = 0; i < BUFFER_SIZE; i++) {
  128. int j = i;
  129. // Lock display to buffer if buffer update deltaTime <= 2^-11
  130. if (module->lissajous)
  131. j = (i + module->bufferIndex) % BUFFER_SIZE;
  132. valuesX[i] = (module->bufferX[j] + offsetX) * gainX / 10.0;
  133. valuesY[i] = (module->bufferY[j] + offsetY) * gainY / 10.0;
  134. }
  135. // Calculate and draw stats
  136. if (++frame >= 4) {
  137. frame = 0;
  138. statsX.calculate(module->bufferX);
  139. statsY.calculate(module->bufferY);
  140. }
  141. drawStats(vg, Vec(0, 20), "X", &statsX);
  142. }
  143. };
  144. struct MinMaxWidget : ModuleWidget {
  145. MinMaxWidget(MinMax *module);
  146. };
  147. MinMaxWidget::MinMaxWidget(MinMax *module) : ModuleWidget(module) {
  148. box.size = Vec(RACK_GRID_WIDTH*6, RACK_GRID_HEIGHT);
  149. {
  150. SVGPanel *panel = new SVGPanel();
  151. panel->box.size = box.size;
  152. panel->setBackground(SVG::load(assetPlugin(plugin, "res/MinMax.svg")));
  153. addChild(panel);
  154. }
  155. addChild(Widget::create<Screw_J>(Vec(16, 1)));
  156. addChild(Widget::create<Screw_J>(Vec(16, 365)));
  157. addChild(Widget::create<Screw_W>(Vec(box.size.x-29, 1)));
  158. addChild(Widget::create<Screw_W>(Vec(box.size.x-29, 365)));
  159. CenteredLabel* const titleLabel = new CenteredLabel(16);
  160. titleLabel->box.pos = Vec(22, 15);
  161. titleLabel->text = "MinMax";
  162. addChild(titleLabel);
  163. {
  164. MinMaxDisplay *display = new MinMaxDisplay();
  165. display->module = module;
  166. display->box.pos = Vec(0, 44);
  167. display->box.size = Vec(box.size.x, 140);
  168. addChild(display);
  169. }
  170. CenteredLabel* const minLabel = new CenteredLabel(12);
  171. minLabel->box.pos = Vec(22, 35);
  172. minLabel->text = "Min";
  173. addChild(minLabel);
  174. CenteredLabel* const maxLabel = new CenteredLabel(12);
  175. maxLabel->box.pos = Vec(22, 60);
  176. maxLabel->text = "Max";
  177. addChild(maxLabel);
  178. CenteredLabel* const timeLabel = new CenteredLabel(12);
  179. timeLabel->box.pos = Vec(22, 101);
  180. timeLabel->text = "Time";
  181. addChild(timeLabel);
  182. CenteredLabel* const inLabel = new CenteredLabel(12);
  183. inLabel->box.pos = Vec(23, 132);
  184. inLabel->text = "Input";
  185. addChild(inLabel);
  186. addParam(ParamWidget::create<SmallWhiteKnob>(Vec(32, 209), module, MinMax::TIME_PARAM, -6.0, -16.0, -14.0));
  187. addInput(Port::create<PJ301MPort>(Vec(33, 275), Port::INPUT, module, MinMax::X_INPUT));
  188. }
  189. } // namespace rack_plugin_JW_Modules
  190. using namespace rack_plugin_JW_Modules;
  191. RACK_PLUGIN_MODEL_INIT(JW_Modules, MinMax) {
  192. Model *modelMinMax = Model::create<MinMax, MinMaxWidget>("JW-Modules", "MinMax", "Min Max", UTILITY_TAG);
  193. return modelMinMax;
  194. }