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.

366 lines
10KB

  1. #include <string.h>
  2. #include "JWModules.hpp"
  3. #include "JWResizableHandle.hpp"
  4. #include "dsp/digital.hpp"
  5. namespace rack_plugin_JW_Modules {
  6. #define BUFFER_SIZE 512
  7. struct FullScope : Module {
  8. enum ParamIds {
  9. X_SCALE_PARAM,
  10. X_POS_PARAM,
  11. Y_SCALE_PARAM,
  12. Y_POS_PARAM,
  13. TIME_PARAM,
  14. LISSAJOUS_PARAM,
  15. TRIG_PARAM,
  16. EXTERNAL_PARAM,
  17. ROTATION_PARAM,
  18. NUM_PARAMS
  19. };
  20. enum InputIds {
  21. X_INPUT,
  22. Y_INPUT,
  23. TRIG_INPUT,
  24. COLOR_INPUT,
  25. TIME_INPUT,
  26. ROTATION_INPUT,
  27. NUM_INPUTS
  28. };
  29. enum OutputIds {
  30. NUM_OUTPUTS
  31. };
  32. float bufferX[BUFFER_SIZE] = {};
  33. float bufferY[BUFFER_SIZE] = {};
  34. int bufferIndex = 0;
  35. float frameIndex = 0;
  36. SchmittTrigger sumTrigger;
  37. SchmittTrigger extTrigger;
  38. bool lissajous = true;
  39. bool external = false;
  40. float lights[4] = {};
  41. SchmittTrigger resetTrigger;
  42. FullScope() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
  43. void step() override;
  44. json_t *toJson() override {
  45. json_t *rootJ = json_object();
  46. json_object_set_new(rootJ, "lissajous", json_integer((int) lissajous));
  47. json_object_set_new(rootJ, "external", json_integer((int) external));
  48. return rootJ;
  49. }
  50. void fromJson(json_t *rootJ) override {
  51. json_t *sumJ = json_object_get(rootJ, "lissajous");
  52. if (sumJ)
  53. lissajous = json_integer_value(sumJ);
  54. json_t *extJ = json_object_get(rootJ, "external");
  55. if (extJ)
  56. external = json_integer_value(extJ);
  57. }
  58. void reset() override {
  59. lissajous = true;
  60. external = false;
  61. }
  62. };
  63. void FullScope::step() {
  64. lights[0] = lissajous ? 0.0 : 1.0;
  65. lights[1] = lissajous ? 1.0 : 0.0;
  66. if (extTrigger.process(params[EXTERNAL_PARAM].value)) {
  67. external = !external;
  68. }
  69. lights[2] = external ? 0.0 : 1.0;
  70. lights[3] = external ? 1.0 : 0.0;
  71. // Compute time
  72. float deltaTime = powf(2.0, params[TIME_PARAM].value + inputs[TIME_INPUT].value);
  73. int frameCount = (int)ceilf(deltaTime * engineGetSampleRate());
  74. // Add frame to buffer
  75. if (bufferIndex < BUFFER_SIZE) {
  76. if (++frameIndex > frameCount) {
  77. frameIndex = 0;
  78. bufferX[bufferIndex] = inputs[X_INPUT].value;
  79. bufferY[bufferIndex] = inputs[Y_INPUT].value;
  80. bufferIndex++;
  81. }
  82. }
  83. // Are we waiting on the next trigger?
  84. if (bufferIndex >= BUFFER_SIZE) {
  85. // Trigger immediately if external but nothing plugged in, or in Lissajous mode
  86. if (lissajous || (external && !inputs[TRIG_INPUT].active)) {
  87. bufferIndex = 0;
  88. frameIndex = 0;
  89. return;
  90. }
  91. // Reset the Schmitt trigger so we don't trigger immediately if the input is high
  92. if (frameIndex == 0) {
  93. resetTrigger.reset();
  94. }
  95. frameIndex++;
  96. // Must go below 0.1V to trigger
  97. // resetTrigger.setThresholds(params[TRIG_PARAM].value - 0.1, params[TRIG_PARAM].value);
  98. float gate = external ? inputs[TRIG_INPUT].value : inputs[X_INPUT].value;
  99. // Reset if triggered
  100. float holdTime = 0.1;
  101. if (resetTrigger.process(gate) || (frameIndex >= engineGetSampleRate() * holdTime)) {
  102. bufferIndex = 0; frameIndex = 0; return;
  103. }
  104. // Reset if we've waited too long
  105. if (frameIndex >= engineGetSampleRate() * holdTime) {
  106. bufferIndex = 0; frameIndex = 0; return;
  107. }
  108. }
  109. }
  110. struct FullScopeDisplay : TransparentWidget {
  111. FullScope *module;
  112. int frame = 0;
  113. float rot = 0;
  114. std::shared_ptr<Font> font;
  115. struct Stats {
  116. float vrms, vpp, vmin, vmax;
  117. void calculate(float *values) {
  118. vrms = 0.0;
  119. vmax = -INFINITY;
  120. vmin = INFINITY;
  121. for (int i = 0; i < BUFFER_SIZE; i++) {
  122. float v = values[i];
  123. vrms += v*v;
  124. vmax = fmaxf(vmax, v);
  125. vmin = fminf(vmin, v);
  126. }
  127. vrms = sqrtf(vrms / BUFFER_SIZE);
  128. vpp = vmax - vmin;
  129. }
  130. };
  131. Stats statsX, statsY;
  132. FullScopeDisplay() {
  133. }
  134. void drawWaveform(NVGcontext *vg, float *valuesX, float *valuesY) {
  135. if (!valuesX)
  136. return;
  137. nvgSave(vg);
  138. Rect b = Rect(Vec(0, 0), box.size);
  139. nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  140. float rotRate = rescalefjw(module->params[FullScope::ROTATION_PARAM].value + module->inputs[FullScope::ROTATION_INPUT].value, 0, 10, 0, 0.5);
  141. if(rotRate != 0){
  142. nvgTranslate(vg, box.size.x/2.0, box.size.y/2.0);
  143. nvgRotate(vg, rot+=rotRate);
  144. nvgTranslate(vg, -box.size.x/2.0, -box.size.y/2.0);
  145. } else {
  146. nvgRotate(vg, 0);
  147. }
  148. nvgBeginPath(vg);
  149. // Draw maximum display left to right
  150. for (int i = 0; i < BUFFER_SIZE; i++) {
  151. float x, y;
  152. if (valuesY) {
  153. x = valuesX[i] / 2.0 + 0.5;
  154. y = valuesY[i] / 2.0 + 0.5;
  155. }
  156. else {
  157. x = (float)i / (BUFFER_SIZE - 1);
  158. y = valuesX[i] / 2.0 + 0.5;
  159. }
  160. Vec p;
  161. p.x = b.pos.x + b.size.x * x;
  162. p.y = b.pos.y + b.size.y * (1.0 - y);
  163. if (i == 0)
  164. nvgMoveTo(vg, p.x, p.y);
  165. else
  166. nvgLineTo(vg, p.x, p.y);
  167. }
  168. nvgLineCap(vg, NVG_ROUND);
  169. nvgMiterLimit(vg, 2.0);
  170. nvgStrokeWidth(vg, 1.5);
  171. nvgGlobalCompositeOperation(vg, NVG_LIGHTER);
  172. nvgStroke(vg);
  173. nvgResetScissor(vg);
  174. nvgRestore(vg);
  175. }
  176. void draw(NVGcontext *vg) {
  177. float gainX = powf(2.0, roundf(module->params[FullScope::X_SCALE_PARAM].value));
  178. float gainY = powf(2.0, roundf(module->params[FullScope::Y_SCALE_PARAM].value));
  179. float offsetX = module->params[FullScope::X_POS_PARAM].value;
  180. float offsetY = module->params[FullScope::Y_POS_PARAM].value;
  181. float valuesX[BUFFER_SIZE];
  182. float valuesY[BUFFER_SIZE];
  183. for (int i = 0; i < BUFFER_SIZE; i++) {
  184. int j = i;
  185. // Lock display to buffer if buffer update deltaTime <= 2^-11
  186. if (module->lissajous)
  187. j = (i + module->bufferIndex) % BUFFER_SIZE;
  188. valuesX[i] = (module->bufferX[j] + offsetX) * gainX / 10.0;
  189. valuesY[i] = (module->bufferY[j] + offsetY) * gainY / 10.0;
  190. }
  191. //color
  192. if(module->inputs[FullScope::COLOR_INPUT].active){
  193. float hue = rescalefjw(module->inputs[FullScope::COLOR_INPUT].value, 0.0, 6.0, 0, 1.0);
  194. nvgStrokeColor(vg, nvgHSLA(hue, 0.5, 0.5, 0xc0));
  195. } else {
  196. nvgStrokeColor(vg, nvgRGBA(25, 150, 252, 0xc0));
  197. }
  198. // Draw waveforms
  199. if (module->lissajous) {
  200. // X x Y
  201. if (module->inputs[FullScope::X_INPUT].active || module->inputs[FullScope::Y_INPUT].active) {
  202. drawWaveform(vg, valuesX, valuesY);
  203. }
  204. }
  205. else {
  206. // Y
  207. if (module->inputs[FullScope::Y_INPUT].active) {
  208. drawWaveform(vg, valuesY, NULL);
  209. }
  210. // X
  211. if (module->inputs[FullScope::X_INPUT].active) {
  212. nvgStrokeColor(vg, nvgRGBA(0x28, 0xb0, 0xf3, 0xc0));
  213. drawWaveform(vg, valuesX, NULL);
  214. }
  215. }
  216. // Calculate stats
  217. if (++frame >= 4) {
  218. frame = 0;
  219. statsX.calculate(module->bufferX);
  220. statsY.calculate(module->bufferY);
  221. }
  222. }
  223. };
  224. struct FullScopeWidget : ModuleWidget {
  225. Panel *panel;
  226. Widget *rightHandle;
  227. TransparentWidget *display;
  228. FullScopeWidget(FullScope *module);
  229. void step() override;
  230. json_t *toJson() override;
  231. void fromJson(json_t *rootJ) override;
  232. Menu *createContextMenu() override;
  233. };
  234. FullScopeWidget::FullScopeWidget(FullScope *module) : ModuleWidget(module) {
  235. box.size = Vec(RACK_GRID_WIDTH*17, RACK_GRID_HEIGHT);
  236. {
  237. panel = new Panel();
  238. panel->backgroundColor = nvgRGB(20, 30, 33);
  239. panel->box.size = box.size;
  240. addChild(panel);
  241. }
  242. JWModuleResizeHandle *leftHandle = new JWModuleResizeHandle(box.size.x);
  243. JWModuleResizeHandle *rightHandle = new JWModuleResizeHandle(box.size.x);
  244. rightHandle->right = true;
  245. this->rightHandle = rightHandle;
  246. addChild(leftHandle);
  247. addChild(rightHandle);
  248. {
  249. FullScopeDisplay *display = new FullScopeDisplay();
  250. display->module = module;
  251. display->box.pos = Vec(0, 0);
  252. display->box.size = Vec(box.size.x, RACK_GRID_HEIGHT);
  253. addChild(display);
  254. this->display = display;
  255. }
  256. int compX = -15, adder = 19;
  257. addInput(Port::create<TinyPJ301MPort>(Vec(compX+=adder, 360), Port::INPUT, module, FullScope::X_INPUT));
  258. addInput(Port::create<TinyPJ301MPort>(Vec(compX+=adder, 360), Port::INPUT, module, FullScope::Y_INPUT));
  259. addInput(Port::create<TinyPJ301MPort>(Vec(compX+=adder, 360), Port::INPUT, module, FullScope::COLOR_INPUT));
  260. addInput(Port::create<TinyPJ301MPort>(Vec(compX+=adder, 360), Port::INPUT, module, FullScope::ROTATION_INPUT));
  261. addInput(Port::create<TinyPJ301MPort>(Vec(compX+=adder, 360), Port::INPUT, module, FullScope::TIME_INPUT));
  262. addParam(ParamWidget::create<JwTinyKnob>(Vec(compX+=adder, 360), module, FullScope::X_POS_PARAM, -10.0, 10.0, 0.0));
  263. addParam(ParamWidget::create<JwTinyKnob>(Vec(compX+=adder, 360), module, FullScope::Y_POS_PARAM, -10.0, 10.0, 0.0));
  264. addParam(ParamWidget::create<JwTinyKnob>(Vec(compX+=adder, 360), module, FullScope::X_SCALE_PARAM, -2.0, 8.0, 1.0));
  265. addParam(ParamWidget::create<JwTinyKnob>(Vec(compX+=adder, 360), module, FullScope::Y_SCALE_PARAM, -2.0, 8.0, 1.0));
  266. addParam(ParamWidget::create<JwTinyKnob>(Vec(compX+=adder, 360), module, FullScope::ROTATION_PARAM, -10.0, 10.0, 0));
  267. addParam(ParamWidget::create<JwTinyKnob>(Vec(compX+=adder, 360), module, FullScope::TIME_PARAM, -6.0, -16.0, -14.0));
  268. addChild(Widget::create<Screw_J>(Vec(compX+25, 362)));
  269. addChild(Widget::create<Screw_W>(Vec(compX+40, 362)));
  270. }
  271. void FullScopeWidget::step() {
  272. panel->box.size = box.size;
  273. display->box.size = Vec(box.size.x, RACK_GRID_HEIGHT);
  274. rightHandle->box.pos.x = box.size.x - rightHandle->box.size.x;
  275. ModuleWidget::step();
  276. }
  277. json_t *FullScopeWidget::toJson() {
  278. json_t *rootJ = ModuleWidget::toJson();
  279. json_object_set_new(rootJ, "width", json_real(box.size.x));
  280. return rootJ;
  281. }
  282. void FullScopeWidget::fromJson(json_t *rootJ) {
  283. ModuleWidget::fromJson(rootJ);
  284. json_t *widthJ = json_object_get(rootJ, "width");
  285. if (widthJ)
  286. box.size.x = json_number_value(widthJ);
  287. }
  288. struct FullScopeLissajousModeMenuItem : MenuItem {
  289. FullScope *fullScope;
  290. void onAction(EventAction &e) override {
  291. fullScope->lissajous = !fullScope->lissajous;
  292. }
  293. void step() override {
  294. rightText = (fullScope->lissajous) ? "✔" : "";
  295. }
  296. };
  297. Menu *FullScopeWidget::createContextMenu() {
  298. Menu *menu = ModuleWidget::createContextMenu();
  299. MenuLabel *spacerLabel = new MenuLabel();
  300. menu->addChild(spacerLabel);
  301. FullScope *fullScope = dynamic_cast<FullScope*>(module);
  302. assert(fullScope);
  303. FullScopeLissajousModeMenuItem *lissMenuItem = new FullScopeLissajousModeMenuItem();
  304. lissMenuItem->text = "Lissajous Mode";
  305. lissMenuItem->fullScope = fullScope;
  306. menu->addChild(lissMenuItem);
  307. return menu;
  308. }
  309. } // namespace rack_plugin_JW_Modules
  310. using namespace rack_plugin_JW_Modules;
  311. RACK_PLUGIN_MODEL_INIT(JW_Modules, FullScope) {
  312. Model *modelFullScope = Model::create<FullScope, FullScopeWidget>("JW-Modules", "FullScope", "Full Scope", VISUAL_TAG);
  313. return modelFullScope;
  314. }