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.

340 lines
9.4KB

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