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.

389 lines
12KB

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