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.

383 lines
11KB

  1. #include <string.h>
  2. #include "plugin.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[16][BUFFER_SIZE] = {};
  33. float bufferY[16][BUFFER_SIZE] = {};
  34. int channelsX = 0;
  35. int channelsY = 0;
  36. int bufferIndex = 0;
  37. int frameIndex = 0;
  38. dsp::BooleanTrigger sumTrigger;
  39. dsp::BooleanTrigger extTrigger;
  40. bool lissajous = false;
  41. bool external = false;
  42. dsp::SchmittTrigger triggers[16];
  43. Scope() {
  44. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  45. configParam(X_SCALE_PARAM, -2.f, 8.f, 0.f, "X scale", " V/div", 1 / 2.f, 5);
  46. configParam(X_POS_PARAM, -10.f, 10.f, 0.f, "X position", " V");
  47. configParam(Y_SCALE_PARAM, -2.f, 8.f, 0.f, "Y scale", " V/div", 1 / 2.f, 5);
  48. configParam(Y_POS_PARAM, -10.f, 10.f, 0.f, "Y position", " V");
  49. const float timeBase = (float) BUFFER_SIZE / 6;
  50. configParam(TIME_PARAM, 6.f, 16.f, 14.f, "Time", " ms/div", 1 / 2.f, 1000 * timeBase);
  51. configParam(LISSAJOUS_PARAM, 0.f, 1.f, 0.f);
  52. configParam(TRIG_PARAM, -10.f, 10.f, 0.f, "Trigger position", " V");
  53. configParam(EXTERNAL_PARAM, 0.f, 1.f, 0.f);
  54. }
  55. void onReset() override {
  56. lissajous = false;
  57. external = false;
  58. std::memset(bufferX, 0, sizeof(bufferX));
  59. std::memset(bufferY, 0, sizeof(bufferY));
  60. }
  61. void process(const ProcessArgs& args) override {
  62. // Modes
  63. if (sumTrigger.process(params[LISSAJOUS_PARAM].getValue() > 0.f)) {
  64. lissajous = !lissajous;
  65. }
  66. lights[PLOT_LIGHT].setBrightness(!lissajous);
  67. lights[LISSAJOUS_LIGHT].setBrightness(lissajous);
  68. if (extTrigger.process(params[EXTERNAL_PARAM].getValue() > 0.f)) {
  69. external = !external;
  70. }
  71. lights[INTERNAL_LIGHT].setBrightness(!external);
  72. lights[EXTERNAL_LIGHT].setBrightness(external);
  73. // Compute time
  74. float deltaTime = std::pow(2.f, -params[TIME_PARAM].getValue());
  75. int frameCount = (int) std::ceil(deltaTime * args.sampleRate);
  76. // Set channels
  77. int channelsX = inputs[X_INPUT].getChannels();
  78. if (channelsX != this->channelsX) {
  79. std::memset(bufferX, 0, sizeof(bufferX));
  80. this->channelsX = channelsX;
  81. }
  82. int channelsY = inputs[Y_INPUT].getChannels();
  83. if (channelsY != this->channelsY) {
  84. std::memset(bufferY, 0, sizeof(bufferY));
  85. this->channelsY = channelsY;
  86. }
  87. // Add frame to buffer
  88. if (bufferIndex < BUFFER_SIZE) {
  89. if (++frameIndex > frameCount) {
  90. frameIndex = 0;
  91. for (int c = 0; c < channelsX; c++) {
  92. bufferX[c][bufferIndex] = inputs[X_INPUT].getVoltage(c);
  93. }
  94. for (int c = 0; c < channelsY; c++) {
  95. bufferY[c][bufferIndex] = inputs[Y_INPUT].getVoltage(c);
  96. }
  97. bufferIndex++;
  98. }
  99. }
  100. // Don't wait for trigger if still filling buffer
  101. if (bufferIndex < BUFFER_SIZE) {
  102. return;
  103. }
  104. // Trigger immediately if external but nothing plugged in, or in Lissajous mode
  105. if (lissajous || (external && !inputs[TRIG_INPUT].isConnected())) {
  106. trigger();
  107. return;
  108. }
  109. frameIndex++;
  110. // Reset if triggered
  111. float trigThreshold = params[TRIG_PARAM].getValue();
  112. Input& trigInput = external ? inputs[TRIG_INPUT] : inputs[X_INPUT];
  113. // This may be 0
  114. int trigChannels = trigInput.getChannels();
  115. for (int c = 0; c < trigChannels; c++) {
  116. float trigVoltage = trigInput.getVoltage(c);
  117. if (triggers[c].process(rescale(trigVoltage, trigThreshold, trigThreshold + 0.001f, 0.f, 1.f))) {
  118. trigger();
  119. return;
  120. }
  121. }
  122. // Reset if we've been waiting for `holdTime`
  123. const float holdTime = 0.5f;
  124. if (frameIndex * args.sampleTime >= holdTime) {
  125. trigger();
  126. return;
  127. }
  128. }
  129. void trigger() {
  130. for (int c = 0; c < 16; c++) {
  131. triggers[c].reset();
  132. }
  133. bufferIndex = 0;
  134. frameIndex = 0;
  135. }
  136. json_t* dataToJson() override {
  137. json_t* rootJ = json_object();
  138. json_object_set_new(rootJ, "lissajous", json_integer((int) lissajous));
  139. json_object_set_new(rootJ, "external", json_integer((int) external));
  140. return rootJ;
  141. }
  142. void dataFromJson(json_t* rootJ) override {
  143. json_t* sumJ = json_object_get(rootJ, "lissajous");
  144. if (sumJ)
  145. lissajous = json_integer_value(sumJ);
  146. json_t* extJ = json_object_get(rootJ, "external");
  147. if (extJ)
  148. external = json_integer_value(extJ);
  149. }
  150. };
  151. struct ScopeDisplay : TransparentWidget {
  152. Scope* module;
  153. int statsFrame = 0;
  154. std::shared_ptr<Font> font;
  155. struct Stats {
  156. float vpp = 0.f;
  157. float vmin = 0.f;
  158. float vmax = 0.f;
  159. void calculate(float* buffer, int channels) {
  160. vmax = -INFINITY;
  161. vmin = INFINITY;
  162. for (int i = 0; i < BUFFER_SIZE * channels; i++) {
  163. float v = buffer[i];
  164. vmax = std::fmax(vmax, v);
  165. vmin = std::fmin(vmin, v);
  166. }
  167. vpp = vmax - vmin;
  168. }
  169. };
  170. Stats statsX, statsY;
  171. ScopeDisplay() {
  172. font = APP->window->loadFont(asset::plugin(pluginInstance, "res/sudo/Sudo.ttf"));
  173. }
  174. void drawWaveform(const DrawArgs& args, float* bufferX, float offsetX, float gainX, float* bufferY, float offsetY, float gainY) {
  175. assert(bufferY);
  176. nvgSave(args.vg);
  177. Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15 * 2)));
  178. nvgScissor(args.vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  179. nvgBeginPath(args.vg);
  180. for (int i = 0; i < BUFFER_SIZE; i++) {
  181. Vec v;
  182. if (bufferX)
  183. v.x = (bufferX[i] + offsetX) * gainX / 2.f + 0.5f;
  184. else
  185. v.x = (float) i / (BUFFER_SIZE - 1);
  186. v.y = (bufferY[i] + offsetY) * gainY / 2.f + 0.5f;
  187. Vec p;
  188. p.x = rescale(v.x, 0.f, 1.f, b.pos.x, b.pos.x + b.size.x);
  189. p.y = rescale(v.y, 0.f, 1.f, b.pos.y + b.size.y, b.pos.y);
  190. if (i == 0)
  191. nvgMoveTo(args.vg, p.x, p.y);
  192. else
  193. nvgLineTo(args.vg, p.x, p.y);
  194. }
  195. nvgLineCap(args.vg, NVG_ROUND);
  196. nvgMiterLimit(args.vg, 2.f);
  197. nvgStrokeWidth(args.vg, 1.5f);
  198. nvgGlobalCompositeOperation(args.vg, NVG_LIGHTER);
  199. nvgStroke(args.vg);
  200. nvgResetScissor(args.vg);
  201. nvgRestore(args.vg);
  202. }
  203. void drawTrig(const DrawArgs& args, float value) {
  204. Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15 * 2)));
  205. nvgScissor(args.vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  206. value = value / 2.f + 0.5f;
  207. Vec p = Vec(box.size.x, b.pos.y + b.size.y * (1.f - value));
  208. // Draw line
  209. nvgStrokeColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x10));
  210. {
  211. nvgBeginPath(args.vg);
  212. nvgMoveTo(args.vg, p.x - 13, p.y);
  213. nvgLineTo(args.vg, 0, p.y);
  214. nvgClosePath(args.vg);
  215. }
  216. nvgStroke(args.vg);
  217. // Draw indicator
  218. nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x60));
  219. {
  220. nvgBeginPath(args.vg);
  221. nvgMoveTo(args.vg, p.x - 2, p.y - 4);
  222. nvgLineTo(args.vg, p.x - 9, p.y - 4);
  223. nvgLineTo(args.vg, p.x - 13, p.y);
  224. nvgLineTo(args.vg, p.x - 9, p.y + 4);
  225. nvgLineTo(args.vg, p.x - 2, p.y + 4);
  226. nvgClosePath(args.vg);
  227. }
  228. nvgFill(args.vg);
  229. nvgFontSize(args.vg, 9);
  230. nvgFontFaceId(args.vg, font->handle);
  231. nvgFillColor(args.vg, nvgRGBA(0x1e, 0x28, 0x2b, 0xff));
  232. nvgText(args.vg, p.x - 8, p.y + 3, "T", NULL);
  233. nvgResetScissor(args.vg);
  234. }
  235. void drawStats(const DrawArgs& args, Vec pos, const char* title, Stats* stats) {
  236. nvgFontSize(args.vg, 13);
  237. nvgFontFaceId(args.vg, font->handle);
  238. nvgTextLetterSpacing(args.vg, -2);
  239. nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x40));
  240. nvgText(args.vg, pos.x + 6, pos.y + 11, title, NULL);
  241. nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x80));
  242. pos = pos.plus(Vec(22, 11));
  243. std::string text;
  244. text = "pp ";
  245. text += isNear(stats->vpp, 0.f, 100.f) ? string::f("% 6.2f", stats->vpp) : " ---";
  246. nvgText(args.vg, pos.x, pos.y, text.c_str(), NULL);
  247. text = "max ";
  248. text += isNear(stats->vmax, 0.f, 100.f) ? string::f("% 6.2f", stats->vmax) : " ---";
  249. nvgText(args.vg, pos.x + 58 * 1, pos.y, text.c_str(), NULL);
  250. text = "min ";
  251. text += isNear(stats->vmin, 0.f, 100.f) ? string::f("% 6.2f", stats->vmin) : " ---";
  252. nvgText(args.vg, pos.x + 58 * 2, pos.y, text.c_str(), NULL);
  253. }
  254. void draw(const DrawArgs& args) override {
  255. if (!module)
  256. return;
  257. float gainX = std::pow(2.f, std::round(module->params[Scope::X_SCALE_PARAM].getValue())) / 10.f;
  258. float gainY = std::pow(2.f, std::round(module->params[Scope::Y_SCALE_PARAM].getValue())) / 10.f;
  259. float offsetX = module->params[Scope::X_POS_PARAM].getValue();
  260. float offsetY = module->params[Scope::Y_POS_PARAM].getValue();
  261. // Draw waveforms
  262. if (module->lissajous) {
  263. // X x Y
  264. int lissajousChannels = std::max(module->channelsX, module->channelsY);
  265. for (int c = 0; c < lissajousChannels; c++) {
  266. nvgStrokeColor(args.vg, nvgRGBA(0x9f, 0xe4, 0x36, 0xc0));
  267. drawWaveform(args, module->bufferX[c], offsetX, gainX, module->bufferY[c], offsetY, gainY);
  268. }
  269. }
  270. else {
  271. // Y
  272. for (int c = 0; c < module->channelsY; c++) {
  273. nvgStrokeColor(args.vg, nvgRGBA(0xe1, 0x02, 0x78, 0xc0));
  274. drawWaveform(args, NULL, 0, 0, module->bufferY[c], offsetY, gainY);
  275. }
  276. // X
  277. for (int c = 0; c < module->channelsX; c++) {
  278. nvgStrokeColor(args.vg, nvgRGBA(0x28, 0xb0, 0xf3, 0xc0));
  279. drawWaveform(args, NULL, 0, 0, module->bufferX[c], offsetX, gainX);
  280. }
  281. float trigThreshold = module->params[Scope::TRIG_PARAM].getValue();
  282. trigThreshold = (trigThreshold + offsetX) * gainX;
  283. drawTrig(args, trigThreshold);
  284. }
  285. // Calculate and draw stats
  286. if (++statsFrame >= 4) {
  287. statsFrame = 0;
  288. statsX.calculate(module->bufferX[0], module->channelsX);
  289. statsY.calculate(module->bufferY[0], module->channelsY);
  290. }
  291. drawStats(args, Vec(0, 0), "X", &statsX);
  292. drawStats(args, Vec(0, box.size.y - 15), "Y", &statsY);
  293. }
  294. };
  295. struct ScopeWidget : ModuleWidget {
  296. ScopeWidget(Scope* module) {
  297. setModule(module);
  298. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Scope.svg")));
  299. addChild(createWidget<ScrewSilver>(Vec(15, 0)));
  300. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 0)));
  301. addChild(createWidget<ScrewSilver>(Vec(15, 365)));
  302. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 365)));
  303. {
  304. ScopeDisplay* display = new ScopeDisplay();
  305. display->module = module;
  306. display->box.pos = Vec(0, 44);
  307. display->box.size = Vec(box.size.x, 140);
  308. addChild(display);
  309. }
  310. addParam(createParam<RoundBlackSnapKnob>(Vec(15, 209), module, Scope::X_SCALE_PARAM));
  311. addParam(createParam<RoundBlackKnob>(Vec(15, 263), module, Scope::X_POS_PARAM));
  312. addParam(createParam<RoundBlackSnapKnob>(Vec(61, 209), module, Scope::Y_SCALE_PARAM));
  313. addParam(createParam<RoundBlackKnob>(Vec(61, 263), module, Scope::Y_POS_PARAM));
  314. addParam(createParam<RoundBlackKnob>(Vec(107, 209), module, Scope::TIME_PARAM));
  315. addParam(createParam<CKD6>(Vec(106, 262), module, Scope::LISSAJOUS_PARAM));
  316. addParam(createParam<RoundBlackKnob>(Vec(153, 209), module, Scope::TRIG_PARAM));
  317. addParam(createParam<CKD6>(Vec(152, 262), module, Scope::EXTERNAL_PARAM));
  318. addInput(createInput<PJ301MPort>(Vec(17, 319), module, Scope::X_INPUT));
  319. addInput(createInput<PJ301MPort>(Vec(63, 319), module, Scope::Y_INPUT));
  320. addInput(createInput<PJ301MPort>(Vec(154, 319), module, Scope::TRIG_INPUT));
  321. addChild(createLight<SmallLight<GreenLight>>(Vec(104, 251), module, Scope::PLOT_LIGHT));
  322. addChild(createLight<SmallLight<GreenLight>>(Vec(104, 296), module, Scope::LISSAJOUS_LIGHT));
  323. addChild(createLight<SmallLight<GreenLight>>(Vec(150, 251), module, Scope::INTERNAL_LIGHT));
  324. addChild(createLight<SmallLight<GreenLight>>(Vec(150, 296), module, Scope::EXTERNAL_LIGHT));
  325. }
  326. };
  327. Model* modelScope = createModel<Scope, ScopeWidget>("Scope");