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.

353 lines
10KB

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