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.

373 lines
10KB

  1. #include <string.h>
  2. #include "Fundamental.hpp"
  3. #include "dsp/digital.hpp"
  4. #define BUFFER_SIZE 512
  5. struct Scope : Module {
  6. enum ParamIds {
  7. X_SCALE_PARAM,
  8. X_POS_PARAM,
  9. Y_SCALE_PARAM,
  10. Y_POS_PARAM,
  11. TIME_PARAM,
  12. LISSAJOUS_PARAM,
  13. TRIG_PARAM,
  14. EXTERNAL_PARAM,
  15. NUM_PARAMS
  16. };
  17. enum InputIds {
  18. X_INPUT,
  19. Y_INPUT,
  20. TRIG_INPUT,
  21. NUM_INPUTS
  22. };
  23. enum OutputIds {
  24. NUM_OUTPUTS
  25. };
  26. enum LightIds {
  27. PLOT_LIGHT,
  28. LISSAJOUS_LIGHT,
  29. INTERNAL_LIGHT,
  30. EXTERNAL_LIGHT,
  31. NUM_LIGHTS
  32. };
  33. float bufferX[BUFFER_SIZE] = {};
  34. float bufferY[BUFFER_SIZE] = {};
  35. int bufferIndex = 0;
  36. float frameIndex = 0;
  37. SchmittTrigger sumTrigger;
  38. SchmittTrigger extTrigger;
  39. bool lissajous = false;
  40. bool external = false;
  41. SchmittTrigger resetTrigger;
  42. Port *xPort, *yPort;
  43. Scope() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  44. void step() override;
  45. json_t *toJson() override {
  46. json_t *rootJ = json_object();
  47. json_object_set_new(rootJ, "lissajous", json_integer((int) lissajous));
  48. json_object_set_new(rootJ, "external", json_integer((int) external));
  49. return rootJ;
  50. }
  51. void fromJson(json_t *rootJ) override {
  52. json_t *sumJ = json_object_get(rootJ, "lissajous");
  53. if (sumJ)
  54. lissajous = json_integer_value(sumJ);
  55. json_t *extJ = json_object_get(rootJ, "external");
  56. if (extJ)
  57. external = json_integer_value(extJ);
  58. }
  59. void reset() override {
  60. lissajous = false;
  61. external = false;
  62. }
  63. };
  64. void Scope::step() {
  65. // Modes
  66. if (sumTrigger.process(params[LISSAJOUS_PARAM].value)) {
  67. lissajous = !lissajous;
  68. }
  69. lights[PLOT_LIGHT].value = lissajous ? 0.0 : 1.0;
  70. lights[LISSAJOUS_LIGHT].value = lissajous ? 1.0 : 0.0;
  71. if (extTrigger.process(params[EXTERNAL_PARAM].value)) {
  72. external = !external;
  73. }
  74. lights[INTERNAL_LIGHT].value = external ? 0.0 : 1.0;
  75. lights[EXTERNAL_LIGHT].value = external ? 1.0 : 0.0;
  76. // Compute time
  77. float deltaTime = powf(2.0, params[TIME_PARAM].value);
  78. int frameCount = (int)ceilf(deltaTime * engineGetSampleRate());
  79. // Add frame to buffer
  80. if (bufferIndex < BUFFER_SIZE) {
  81. if (++frameIndex > frameCount) {
  82. frameIndex = 0;
  83. bufferX[bufferIndex] = inputs[X_INPUT].value;
  84. bufferY[bufferIndex] = inputs[Y_INPUT].value;
  85. bufferIndex++;
  86. }
  87. }
  88. // Are we waiting on the next trigger?
  89. if (bufferIndex >= BUFFER_SIZE) {
  90. // Trigger immediately if external but nothing plugged in, or in Lissajous mode
  91. if (lissajous || (external && !inputs[TRIG_INPUT].active)) {
  92. bufferIndex = 0;
  93. frameIndex = 0;
  94. return;
  95. }
  96. // Reset the Schmitt trigger so we don't trigger immediately if the input is high
  97. if (frameIndex == 0) {
  98. resetTrigger.reset();
  99. }
  100. frameIndex++;
  101. // Must go below 0.1V to trigger
  102. resetTrigger.setThresholds(params[TRIG_PARAM].value - 0.1, params[TRIG_PARAM].value);
  103. float gate = external ? inputs[TRIG_INPUT].value : inputs[X_INPUT].value;
  104. // Reset if triggered
  105. float holdTime = 0.1;
  106. if (resetTrigger.process(gate) || (frameIndex >= engineGetSampleRate() * holdTime)) {
  107. bufferIndex = 0; frameIndex = 0; return;
  108. }
  109. // Reset if we've waited too long
  110. if (frameIndex >= engineGetSampleRate() * holdTime) {
  111. bufferIndex = 0; frameIndex = 0; return;
  112. }
  113. }
  114. }
  115. struct ScopeDisplay : TransparentWidget {
  116. Scope *module;
  117. int frame = 0;
  118. std::shared_ptr<Font> font;
  119. struct Stats {
  120. float vrms, vpp, vmin, vmax;
  121. void calculate(float *values) {
  122. vrms = 0.0;
  123. vmax = -INFINITY;
  124. vmin = INFINITY;
  125. for (int i = 0; i < BUFFER_SIZE; i++) {
  126. float v = values[i];
  127. vrms += v*v;
  128. vmax = fmaxf(vmax, v);
  129. vmin = fminf(vmin, v);
  130. }
  131. vrms = sqrtf(vrms / BUFFER_SIZE);
  132. vpp = vmax - vmin;
  133. }
  134. };
  135. Stats statsX, statsY;
  136. ScopeDisplay() {
  137. font = Font::load(assetPlugin(plugin, "res/fonts/Sudo.ttf"));
  138. }
  139. void drawWaveform(NVGcontext *vg, float *valuesX, float *valuesY) {
  140. if (!valuesX)
  141. return;
  142. nvgSave(vg);
  143. Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15*2)));
  144. nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  145. nvgBeginPath(vg);
  146. // Draw maximum display left to right
  147. for (int i = 0; i < BUFFER_SIZE; i++) {
  148. float x, y;
  149. if (valuesY) {
  150. x = valuesX[i] / 2.0 + 0.5;
  151. y = valuesY[i] / 2.0 + 0.5;
  152. }
  153. else {
  154. x = (float)i / (BUFFER_SIZE - 1);
  155. y = valuesX[i] / 2.0 + 0.5;
  156. }
  157. Vec p;
  158. p.x = b.pos.x + b.size.x * x;
  159. p.y = b.pos.y + b.size.y * (1.0 - y);
  160. if (i == 0)
  161. nvgMoveTo(vg, p.x, p.y);
  162. else
  163. nvgLineTo(vg, p.x, p.y);
  164. }
  165. nvgLineCap(vg, NVG_ROUND);
  166. nvgMiterLimit(vg, 2.0);
  167. nvgStrokeWidth(vg, 1.5);
  168. nvgGlobalCompositeOperation(vg, NVG_LIGHTER);
  169. nvgStroke(vg);
  170. nvgResetScissor(vg);
  171. nvgRestore(vg);
  172. }
  173. void drawTrig(NVGcontext *vg, float value) {
  174. Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15*2)));
  175. nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  176. value = value / 2.0 + 0.5;
  177. Vec p = Vec(box.size.x, b.pos.y + b.size.y * (1.0 - value));
  178. // Draw line
  179. nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x10));
  180. {
  181. nvgBeginPath(vg);
  182. nvgMoveTo(vg, p.x - 13, p.y);
  183. nvgLineTo(vg, 0, p.y);
  184. nvgClosePath(vg);
  185. }
  186. nvgStroke(vg);
  187. // Draw indicator
  188. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x60));
  189. {
  190. nvgBeginPath(vg);
  191. nvgMoveTo(vg, p.x - 2, p.y - 4);
  192. nvgLineTo(vg, p.x - 9, p.y - 4);
  193. nvgLineTo(vg, p.x - 13, p.y);
  194. nvgLineTo(vg, p.x - 9, p.y + 4);
  195. nvgLineTo(vg, p.x - 2, p.y + 4);
  196. nvgClosePath(vg);
  197. }
  198. nvgFill(vg);
  199. nvgFontSize(vg, 9);
  200. nvgFontFaceId(vg, font->handle);
  201. nvgFillColor(vg, nvgRGBA(0x1e, 0x28, 0x2b, 0xff));
  202. nvgText(vg, p.x - 8, p.y + 3, "T", NULL);
  203. nvgResetScissor(vg);
  204. }
  205. void drawStats(NVGcontext *vg, Vec pos, const char *title, Stats *stats) {
  206. nvgFontSize(vg, 13);
  207. nvgFontFaceId(vg, font->handle);
  208. nvgTextLetterSpacing(vg, -2);
  209. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x40));
  210. nvgText(vg, pos.x + 6, pos.y + 11, title, NULL);
  211. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x80));
  212. char text[128];
  213. snprintf(text, sizeof(text), "pp % 06.2f max % 06.2f min % 06.2f", stats->vpp, stats->vmax, stats->vmin);
  214. nvgText(vg, pos.x + 22, pos.y + 11, text, NULL);
  215. }
  216. void draw(NVGcontext *vg) override {
  217. float gainX = powf(2.0, roundf(module->params[Scope::X_SCALE_PARAM].value));
  218. float gainY = powf(2.0, roundf(module->params[Scope::Y_SCALE_PARAM].value));
  219. float offsetX = module->params[Scope::X_POS_PARAM].value;
  220. float offsetY = module->params[Scope::Y_POS_PARAM].value;
  221. float valuesX[BUFFER_SIZE];
  222. float valuesY[BUFFER_SIZE];
  223. for (int i = 0; i < BUFFER_SIZE; i++) {
  224. int j = i;
  225. // Lock display to buffer if buffer update deltaTime <= 2^-11
  226. if (module->lissajous)
  227. j = (i + module->bufferIndex) % BUFFER_SIZE;
  228. valuesX[i] = (module->bufferX[j] + offsetX) * gainX / 10.0;
  229. valuesY[i] = (module->bufferY[j] + offsetY) * gainY / 10.0;
  230. }
  231. // Draw waveforms
  232. if (module->lissajous) {
  233. // X x Y
  234. if (module->inputs[Scope::X_INPUT].active || module->inputs[Scope::Y_INPUT].active) {
  235. nvgStrokeColor(vg, getPortColor(nullptr));
  236. drawWaveform(vg, valuesX, valuesY);
  237. }
  238. }
  239. else {
  240. // Y
  241. NVGcolor xColor, yColor;
  242. xColor = getPortColor(module->xPort);
  243. yColor = getPortColor(module->yPort);
  244. if (memcmp(&xColor, &yColor, sizeof(xColor)) == 0 ) {
  245. // if the colors are the same pick novel colors
  246. NVGcolor tmp = xColor;
  247. tmp.r = xColor.g;
  248. tmp.g = xColor.b;
  249. tmp.b = xColor.r;
  250. xColor = tmp;
  251. tmp.r = yColor.b;
  252. tmp.g = yColor.r;
  253. tmp.b = yColor.g;
  254. yColor = tmp;
  255. }
  256. if (module->inputs[Scope::Y_INPUT].active) {
  257. nvgStrokeColor(vg, yColor);
  258. drawWaveform(vg, valuesY, NULL);
  259. }
  260. // X
  261. if (module->inputs[Scope::X_INPUT].active) {
  262. nvgStrokeColor(vg, xColor);
  263. drawWaveform(vg, valuesX, NULL);
  264. }
  265. float valueTrig = (module->params[Scope::TRIG_PARAM].value + offsetX) * gainX / 10.0;
  266. drawTrig(vg, valueTrig);
  267. }
  268. // Calculate and draw stats
  269. if (++frame >= 4) {
  270. frame = 0;
  271. statsX.calculate(module->bufferX);
  272. statsY.calculate(module->bufferY);
  273. }
  274. drawStats(vg, Vec(0, 0), "X", &statsX);
  275. drawStats(vg, Vec(0, box.size.y - 15), "Y", &statsY);
  276. }
  277. NVGcolor getPortColor(Port* port) {
  278. WireWidget *wire = gRackWidget->wireContainer->getTopWire(port);
  279. NVGcolor color = wire ? wire->color : nvgRGBA(0x9f, 0xe4, 0x36, 0xc0);
  280. //color.a = 0xc0;
  281. return color;
  282. }
  283. };
  284. ScopeWidget::ScopeWidget() {
  285. Scope *module = new Scope();
  286. setModule(module);
  287. box.size = Vec(15*13, 380);
  288. {
  289. SVGPanel *panel = new SVGPanel();
  290. panel->box.size = box.size;
  291. panel->setBackground(SVG::load(assetPlugin(plugin, "res/Scope.svg")));
  292. addChild(panel);
  293. }
  294. addChild(createScrew<ScrewSilver>(Vec(15, 0)));
  295. addChild(createScrew<ScrewSilver>(Vec(box.size.x-30, 0)));
  296. addChild(createScrew<ScrewSilver>(Vec(15, 365)));
  297. addChild(createScrew<ScrewSilver>(Vec(box.size.x-30, 365)));
  298. {
  299. ScopeDisplay *display = new ScopeDisplay();
  300. display->module = module;
  301. display->box.pos = Vec(0, 44);
  302. display->box.size = Vec(box.size.x, 140);
  303. addChild(display);
  304. }
  305. addParam(createParam<RoundSmallBlackSnapKnob>(Vec(15, 209), module, Scope::X_SCALE_PARAM, -2.0, 8.0, 0.0));
  306. addParam(createParam<RoundSmallBlackKnob>(Vec(15, 263), module, Scope::X_POS_PARAM, -10.0, 10.0, 0.0));
  307. addParam(createParam<RoundSmallBlackSnapKnob>(Vec(61, 209), module, Scope::Y_SCALE_PARAM, -2.0, 8.0, 0.0));
  308. addParam(createParam<RoundSmallBlackKnob>(Vec(61, 263), module, Scope::Y_POS_PARAM, -10.0, 10.0, 0.0));
  309. addParam(createParam<RoundSmallBlackKnob>(Vec(107, 209), module, Scope::TIME_PARAM, -6.0, -16.0, -14.0));
  310. addParam(createParam<CKD6>(Vec(106, 262), module, Scope::LISSAJOUS_PARAM, 0.0, 1.0, 0.0));
  311. addParam(createParam<RoundSmallBlackKnob>(Vec(153, 209), module, Scope::TRIG_PARAM, -10.0, 10.0, 0.0));
  312. addParam(createParam<CKD6>(Vec(152, 262), module, Scope::EXTERNAL_PARAM, 0.0, 1.0, 0.0));
  313. module->xPort = dynamic_cast<Port*>(createInput<PJ301MPort>(Vec(17, 319), module, Scope::X_INPUT));
  314. addInput(module->xPort);
  315. module->yPort = dynamic_cast<Port*>(createInput<PJ301MPort>(Vec(63, 319), module, Scope::Y_INPUT));
  316. addInput(module->yPort);
  317. addInput(createInput<PJ301MPort>(Vec(154, 319), module, Scope::TRIG_INPUT));
  318. addChild(createLight<SmallLight<GreenLight>>(Vec(104, 251), module, Scope::PLOT_LIGHT));
  319. addChild(createLight<SmallLight<GreenLight>>(Vec(104, 296), module, Scope::LISSAJOUS_LIGHT));
  320. addChild(createLight<SmallLight<GreenLight>>(Vec(150, 251), module, Scope::INTERNAL_LIGHT));
  321. addChild(createLight<SmallLight<GreenLight>>(Vec(150, 296), module, Scope::EXTERNAL_LIGHT));
  322. }