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.

493 lines
16KB

  1. #include "Features.hpp"
  2. #if USE_NEW_SCOPE
  3. #include <string.h>
  4. #include "trowaSoft.hpp"
  5. #include "trowaSoftComponents.hpp"
  6. #include "trowaSoftUtilities.hpp"
  7. #include "dsp/digital.hpp"
  8. #include "Module_multiScope.hpp"
  9. #include "TSScopeBase.hpp"
  10. #include "Widget_multiScope.hpp"
  11. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  12. // multiScope()
  13. // Multi scope.
  14. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  15. multiScope::multiScope() : Module(multiScope::NUM_PARAMS, multiScope::NUM_INPUTS, multiScope::NUM_OUTPUTS, multiScope::NUM_LEDS)
  16. {
  17. initialized = false;
  18. firstLoad = true;
  19. plotBackgroundColor = COLOR_BLACK;
  20. float initColorKnobs[4] = { -10, -3.33, 3, 7.2 };
  21. for (int wIx = 0; wIx < TROWA_SCOPE_NUM_WAVEFORMS; wIx++)
  22. {
  23. waveForms[wIx] = new TSWaveform();
  24. waveForms[wIx]->setHueFromKnob(initColorKnobs[wIx]);
  25. waveForms[wIx]->setFillHueFromKnob(initColorKnobs[wIx]);
  26. }
  27. return;
  28. } // end multiScope()
  29. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  30. // ~multiScope()
  31. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  32. multiScope::~multiScope()
  33. {
  34. // Clean our stuff
  35. for (int wIx = 0; wIx < TROWA_SCOPE_NUM_WAVEFORMS; wIx++)
  36. {
  37. delete waveForms[wIx];
  38. }
  39. return;
  40. } // end multiScope()
  41. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  42. // step(void)
  43. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  44. void multiScope::step() {
  45. if (!initialized)
  46. return;
  47. #if ENABLE_BG_COLOR_PICKER
  48. if (plotBackgroundDisplayOnTrigger.process(params[multiScope::BGCOLOR_DISPLAY_PARAM].value))
  49. {
  50. showColorPicker = !showColorPicker;
  51. if (showColorPicker)
  52. {
  53. // Load Background color:
  54. editColorPointer = &(this->plotBackgroundColor);
  55. }
  56. }
  57. lights[multiScope::BGCOLOR_DISPLAY_LED].value = showColorPicker;
  58. #endif
  59. //TSWaveform* waveForm = NULL;
  60. for (int wIx = 0; wIx < TROWA_SCOPE_NUM_WAVEFORMS; wIx++)
  61. {
  62. //waveForm = waveForms[wIx]; // tmp pointer
  63. // Effect:
  64. waveForms[wIx]->gEffectIx = (int)clamp(static_cast<int>(roundf(params[multiScope::EFFECT_PARAM+wIx].value)), 0, TROWA_SCOPE_NUM_EFFECTS - 1);
  65. // Lissajous:
  66. if (waveForms[wIx]->lissajousTrigger.process(params[multiScope::LISSAJOUS_PARAM + wIx].value))
  67. {
  68. waveForms[wIx]->lissajous = !waveForms[wIx]->lissajous;
  69. }
  70. lights[multiScope::LISSAJOUS_LED + wIx].value = waveForms[wIx]->lissajous;
  71. // Compute Color:
  72. float hue = 0;
  73. if(inputs[multiScope::COLOR_INPUT+wIx].active){
  74. hue = clamp(rescale(inputs[multiScope::COLOR_INPUT+wIx].value, TROWA_SCOPE_HUE_INPUT_MIN_V, TROWA_SCOPE_HUE_INPUT_MAX_V, 0.0, 1.0), 0.0, 1.0);
  75. } else {
  76. hue = rescale(params[multiScope::COLOR_PARAM+wIx].value, TROWA_SCOPE_HUE_KNOB_MIN, TROWA_SCOPE_HUE_KNOB_MAX, 0.0, 1.0);
  77. }
  78. waveForms[wIx]->colorChanged = hue != waveForms[wIx]->waveHue || firstLoad;
  79. if (waveForms[wIx]->colorChanged)
  80. {
  81. waveForms[wIx]->waveHue = hue;
  82. if (hue > 0.99)
  83. {
  84. // Inject white
  85. waveForms[wIx]->waveColor = COLOR_WHITE;
  86. }
  87. else
  88. {
  89. waveForms[wIx]->waveColor = HueToColor(waveForms[wIx]->waveHue); // Base Color (opacity full)
  90. }
  91. #if TROWA_SCOPE_USE_COLOR_LIGHTS
  92. // Change the light color:
  93. waveForms[wIx]->waveLight->setColor(waveForms[wIx]->waveColor);
  94. #endif
  95. } // end if color changed
  96. // Opacity:
  97. if (inputs[multiScope::OPACITY_INPUT + wIx].active)
  98. {
  99. waveForms[wIx]->waveOpacity = clamp(rescale(inputs[multiScope::OPACITY_INPUT + wIx].value, TROWA_SCOPE_OPACITY_INPUT_MIN, TROWA_SCOPE_OPACITY_INPUT_MAX, TROWA_SCOPE_MIN_OPACITY, TROWA_SCOPE_MAX_OPACITY),
  100. TROWA_SCOPE_MIN_OPACITY, TROWA_SCOPE_MAX_OPACITY);
  101. }
  102. else
  103. {
  104. waveForms[wIx]->waveOpacity = params[multiScope::OPACITY_PARAM + wIx].value;
  105. }
  106. // Line Thickness
  107. if (inputs[multiScope::THICKNESS_INPUT + wIx].active)
  108. {
  109. waveForms[wIx]->lineThickness = clamp(rescale(inputs[multiScope::THICKNESS_INPUT + wIx].value, TROWA_SCOPE_THICKNESS_INPUT_MIN, TROWA_SCOPE_THICKNESS_INPUT_MAX, TROWA_SCOPE_THICKNESS_MIN, TROWA_SCOPE_THICKNESS_MAX),
  110. TROWA_SCOPE_THICKNESS_MIN, TROWA_SCOPE_THICKNESS_MAX);
  111. }
  112. else
  113. {
  114. waveForms[wIx]->lineThickness = params[multiScope::THICKNESS_PARAM + wIx].value;
  115. }
  116. // Compute Fill :::::::::::::::::::::::::::::::::::::::::
  117. if (waveForms[wIx]->fillOnTrigger.process(params[multiScope::FILL_ON_PARAM + wIx].value))
  118. {
  119. waveForms[wIx]->doFill = !waveForms[wIx]->doFill;
  120. //debug("Waveform %d: Fill On Clicked : %d (ParamId: %d).", wIx, waveForms[wIx]->doFill, multiScope::FILL_ON_PARAM + wIx);
  121. }
  122. lights[multiScope::FILL_ON_LED + wIx].value = waveForms[wIx]->doFill;
  123. hue = 0;
  124. if (inputs[multiScope::FILL_COLOR_INPUT + wIx].active) {
  125. hue = clamp(rescale(inputs[multiScope::FILL_COLOR_INPUT + wIx].value, TROWA_SCOPE_HUE_INPUT_MIN_V, TROWA_SCOPE_HUE_INPUT_MAX_V, 0.0, 1.0), 0.0, 1.0);
  126. }
  127. else {
  128. hue = rescale(params[multiScope::FILL_COLOR_PARAM + wIx].value, TROWA_SCOPE_HUE_KNOB_MIN, TROWA_SCOPE_HUE_KNOB_MAX, 0.0, 1.0);
  129. }
  130. if (hue != waveForms[wIx]->fillHue || firstLoad)
  131. {
  132. waveForms[wIx]->fillHue = hue;
  133. if (hue > 0.99)
  134. {
  135. // Inject White
  136. waveForms[wIx]->fillColor = COLOR_WHITE;
  137. }
  138. else
  139. {
  140. waveForms[wIx]->fillColor = HueToColor(waveForms[wIx]->fillHue); // Base Color (opacity full)
  141. }
  142. } // end if color changed
  143. // Opacity:
  144. if (inputs[multiScope::FILL_OPACITY_INPUT + wIx].active)
  145. {
  146. waveForms[wIx]->fillOpacity = clamp(rescale(inputs[multiScope::FILL_OPACITY_INPUT + wIx].value, TROWA_SCOPE_OPACITY_INPUT_MIN, TROWA_SCOPE_OPACITY_INPUT_MAX, TROWA_SCOPE_MIN_OPACITY, TROWA_SCOPE_MAX_OPACITY),
  147. TROWA_SCOPE_MIN_OPACITY, TROWA_SCOPE_MAX_OPACITY);
  148. }
  149. else
  150. {
  151. waveForms[wIx]->fillOpacity = params[multiScope::FILL_OPACITY_PARAM + wIx].value;
  152. }
  153. // Compute rotation:
  154. waveForms[wIx]->rotKnobValue = params[multiScope::ROTATION_PARAM+wIx].value;
  155. if (waveForms[wIx]->rotModeTrigger.process(params[multiScope::ROTATION_MODE_PARAM+wIx].value))
  156. {
  157. waveForms[wIx]->rotMode = !waveForms[wIx]->rotMode;
  158. //debug("Waveform %d: Rotation Mode On Clicked : %d.", wIx, waveForms[wIx]->rotMode);
  159. }
  160. lights[multiScope::ROT_LED+wIx].value = waveForms[wIx]->rotMode;
  161. float rot = 0;
  162. float rotRate = 0;
  163. if (waveForms[wIx]->rotMode)
  164. {
  165. // Absolute position:
  166. rot = rescale(params[multiScope::ROTATION_PARAM+wIx].value + inputs[multiScope::ROTATION_INPUT+wIx].value, 0, 10, 0, NVG_PI);
  167. }
  168. else
  169. {
  170. // Differential rotation
  171. rotRate = rescale(params[multiScope::ROTATION_PARAM+wIx].value + inputs[multiScope::ROTATION_INPUT+wIx].value, 0, 10, 0, 0.5);
  172. }
  173. waveForms[wIx]->rotAbsValue = rot;
  174. waveForms[wIx]->rotDiffValue = rotRate;
  175. // Compute time:
  176. float deltaTime = powf(2.0, params[TIME_PARAM+wIx].value + inputs[TIME_INPUT+wIx].value);
  177. int frameCount = (int)ceilf(deltaTime * engineGetSampleRate());
  178. // Add frame to buffer
  179. if (waveForms[wIx]->bufferIndex < BUFFER_SIZE) {
  180. if (++(waveForms[wIx]->frameIndex) > frameCount) {
  181. waveForms[wIx]->frameIndex = 0;
  182. waveForms[wIx]->bufferX[waveForms[wIx]->bufferIndex] = inputs[X_INPUT+wIx].value;
  183. waveForms[wIx]->bufferY[waveForms[wIx]->bufferIndex] = inputs[Y_INPUT+wIx].value;
  184. waveForms[wIx]->bufferPenOn[waveForms[wIx]->bufferIndex] = (!inputs[PEN_ON_INPUT + wIx].active || inputs[PEN_ON_INPUT + wIx].value > 0.1); // Allow some noise?
  185. waveForms[wIx]->bufferIndex++;
  186. }
  187. }
  188. else {
  189. if (waveForms[wIx]->lissajous)
  190. {
  191. // Reset
  192. waveForms[wIx]->bufferIndex = 0;
  193. waveForms[wIx]->frameIndex = 0;
  194. }
  195. else
  196. {
  197. // Just show stuff (no trigger inputs)
  198. waveForms[wIx]->frameIndex++;
  199. float holdTime = 0.1;
  200. if (waveForms[wIx]->frameIndex >= engineGetSampleRate() * holdTime) {
  201. waveForms[wIx]->bufferIndex = 0;
  202. waveForms[wIx]->frameIndex = 0;
  203. }
  204. }
  205. }
  206. } // end loop through waveforms
  207. firstLoad = false;
  208. return;
  209. } // end step()
  210. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  211. // drawWaveform()
  212. // @vg : (IN) NVGcontext
  213. // @valX: (IN) Pointer to x values.
  214. // @valY: (IN) Pointer to y values.
  215. // @rotRate: (IN) Rotation rate in radians
  216. // @lineThickness: (IN) Line thickness
  217. // @compositeOp: (IN) Some global effect if any
  218. // @flipX: (IN) Flip along x (at x=0)
  219. // @flipY: (IN) Flip along y
  220. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  221. void multiScopeDisplay::drawWaveform(NVGcontext *vg, float *valX, float *valY, bool* penOn,
  222. float rotRate, float lineThickness, NVGcolor lineColor,
  223. bool doFill, NVGcolor fillColor,
  224. NVGcompositeOperation compositeOp, bool flipX, bool flipY)
  225. {
  226. if (!valX)
  227. return;
  228. nvgSave(vg);
  229. Rect b = Rect(Vec(0, 0), box.size);
  230. nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  231. //nvgTranslate(vg, box.size.x / 2.0, box.size.y / 2.0);
  232. //nvgRotate(vg, rot += rotRate);
  233. rot += rotRate;
  234. if (flipX || flipY)
  235. {
  236. // Sets the transform to scale matrix.
  237. // void nvgTransformScale(float* dst, float sx, float sy);
  238. nvgScale(vg, ((flipX) ? -1 : 1), (flipY) ? -1 : 1); // flip
  239. }
  240. // Draw maximum display left to right
  241. nvgBeginPath(vg);
  242. float xOffset = 0;// -box.size.x / 2.0;
  243. float yOffset = 0;// -box.size.y / 2.0;
  244. float minX = b.pos.x + xOffset + lineThickness / 2.0;
  245. float maxX = minX + b.size.x - lineThickness;
  246. float minY = b.pos.y + yOffset + lineThickness / 2.0;
  247. float maxY = minY + box.size.y - lineThickness;
  248. bool doTrim = false;
  249. switch (compositeOp)
  250. {
  251. case NVG_DESTINATION_OVER:
  252. case NVG_SOURCE_IN:
  253. case NVG_SOURCE_OUT:
  254. case NVG_DESTINATION_IN:
  255. case NVG_DESTINATION_ATOP:
  256. case NVG_COPY:
  257. doTrim = true;
  258. break;
  259. case NVG_SOURCE_OVER:
  260. case NVG_ATOP:
  261. case NVG_DESTINATION_OUT:
  262. case NVG_LIGHTER:
  263. case NVG_XOR:
  264. default:
  265. break;
  266. }
  267. float ox = b.pos.x + b.size.x / 2.0; // Center of box
  268. float oy = b.pos.y + b.size.y / 2.0;
  269. float s = sin(rot);
  270. float c = cos(rot);
  271. bool lastPointStarted = false; // If the last point was actually plotted
  272. //bool lastPointInBounds = false; // If the last point was in bounds
  273. Vec lastPoint;
  274. Vec lastPointRaw;
  275. uint8_t lastLocCodeRaw = POINT_POS_INSIDE;
  276. bool lastPointExists = false; // If the last point was actually calculated (i.e. false if pen is off)
  277. uint8_t lastLocCode = POINT_POS_INSIDE;
  278. for (int i = 0; i < BUFFER_SIZE; i++) {
  279. if (penOn[i])
  280. {
  281. float x, y;
  282. if (valY) {
  283. x = valX[i] / 2.0 + 0.5;
  284. y = valY[i] / 2.0 + 0.5;
  285. }
  286. else {
  287. x = (float)i / (BUFFER_SIZE - 1);
  288. y = valX[i] / 2.0 + 0.5;
  289. }
  290. Vec p;
  291. //p.x = b.pos.x + xOffset + b.size.x * x;
  292. //p.y = b.pos.y + yOffset + b.size.y * (1.0 - y);
  293. p.x = b.size.x * x;
  294. p.y = b.size.y * (1.0 - y);
  295. // Rotate ourselves so we can cull easily.
  296. double dx = p.x - ox;
  297. double dy = p.y - oy;
  298. p.x = ox + dx * c - dy * s;
  299. p.y = oy + dx * s + dy * c;
  300. bool plotPoint = true;
  301. if (doTrim)
  302. {
  303. Vec origPoint = p;
  304. // Do some cropping/clipping if needed
  305. uint8_t locCode = GetPointLocationCode(p, minX, maxX, minY, maxY);
  306. uint8_t origLocCode = locCode;
  307. //bool inBounds = !locCode;
  308. bool doSearch = false;
  309. if (locCode)
  310. {
  311. // Outside of bounds
  312. if (lastPointExists && !LINE_OUT_OF_BOUNDS(locCode, lastLocCodeRaw)) // If there was a point last time and both prev and this one don't make a line totally outside of bounds.
  313. {
  314. // Check the last point calculated (it may be out of bounds too)
  315. doSearch = true;
  316. }
  317. else
  318. {
  319. // Just save this for next time. Do not plot
  320. plotPoint = false;
  321. }
  322. } // end if this point is out of bounds
  323. else if (lastPointExists && lastLocCodeRaw)
  324. {
  325. // Last point wasn't valid although this one is, so we will have to inject both points.
  326. doSearch = true;
  327. }
  328. if (doSearch)
  329. {
  330. Vec p1 = lastPointRaw;
  331. Vec p2 = p;
  332. uint8_t outcode0 = lastLocCodeRaw;
  333. uint8_t outcode1 = locCode;
  334. while (doSearch)
  335. {
  336. if (LINE_IS_IN_BOUNDS(outcode0, outcode1))
  337. { // Bitwise OR is 0. Trivially accept and get out of loop
  338. plotPoint = true;
  339. doSearch = false;
  340. }
  341. else if (LINE_OUT_OF_BOUNDS(outcode0, outcode1))
  342. { // Bitwise AND is not 0. (implies both end points are in the same region outside the window). Reject and get out of loop
  343. doSearch = false;
  344. plotPoint = false;
  345. }
  346. else {
  347. // failed both tests, so calculate the line segment to clip
  348. // from an outside point to an intersection with clip edge
  349. double x, y;
  350. // At least one endpoint is outside the clip rectangle; pick it.
  351. uint8_t outcodeOut = outcode0 ? outcode0 : outcode1;
  352. // Now find the intersection point;
  353. // use formulas:
  354. // slope = (y1 - y0) / (x1 - x0)
  355. // x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax
  356. // y = y0 + slope * (xm - x0), where xm is xmin or xmax
  357. if (outcodeOut & POINT_POS_TOP) { // point is above the clip rectangle
  358. x = p1.x + (p2.x - p1.x) * (maxY - p1.y) / (p2.y - p1.y);
  359. y = maxY;
  360. }
  361. else if (outcodeOut & POINT_POS_BOTTOM) { // point is below the clip rectangle
  362. x = p1.x + (p2.x - p1.x) * (minY - p1.y) / (p2.y - p1.y);
  363. y = minY;
  364. }
  365. else if (outcodeOut & POINT_POS_RIGHT) { // point is to the right of clip rectangle
  366. y = p1.y + (p2.y - p1.y) * (maxX - p1.x) / (p2.x - p1.x);
  367. x = maxX;
  368. }
  369. else if (outcodeOut & POINT_POS_LEFT) { // point is to the left of clip rectangle
  370. y = p1.y + (p2.y - p1.y) * (minX - p1.x) / (p2.x - p1.x);
  371. x = minX;
  372. }
  373. // Now we move outside point to intersection point to clip
  374. // and get ready for next pass.
  375. if (outcodeOut == outcode0) {
  376. p1.x = x;
  377. p1.y = y;
  378. outcode0 = GetPointLocationCode(p1, minX, maxX, minY, maxY);
  379. }
  380. else {
  381. p2.x = x;
  382. p2.y = y;
  383. outcode1 = GetPointLocationCode(p2, minX, maxX, minY, maxY);
  384. }
  385. } // end else (check bounds)
  386. } // end while
  387. // See if we should plot the last point (now that it's fixed)
  388. if (lastLocCode && !outcode0)
  389. {
  390. // Last point was out of bounds, but is now not out of bounds
  391. if (!lastPointStarted)
  392. {
  393. nvgMoveTo(vg, p1.x, p1.y);
  394. }
  395. else
  396. {
  397. nvgLineTo(vg, p1.x, p1.y);
  398. }
  399. lastPointStarted = true;
  400. } // end if plot prev point
  401. locCode = outcode1;
  402. p = p2;
  403. if (locCode)
  404. {
  405. // Still not in bounds
  406. plotPoint = false;
  407. }
  408. }
  409. lastLocCode = locCode;
  410. lastPointRaw = origPoint;
  411. lastLocCodeRaw = origLocCode;
  412. } // end if do trimming
  413. if (plotPoint)
  414. {
  415. if (!lastPointStarted)
  416. {
  417. nvgMoveTo(vg, p.x, p.y);
  418. }
  419. else
  420. {
  421. nvgLineTo(vg, p.x, p.y);
  422. }
  423. lastPointStarted = true;
  424. }
  425. else
  426. {
  427. lastPointStarted = false;
  428. }
  429. lastPoint = p;
  430. lastPointExists = true;
  431. } // end if penOn
  432. else
  433. {
  434. // Pen is off, ignore this point
  435. lastPointStarted = false;
  436. lastPointExists = false;
  437. } // end else (pen off)
  438. } // end loop through buffer
  439. nvgLineCap(vg, NVG_ROUND);
  440. nvgMiterLimit(vg, 2.0);
  441. nvgGlobalCompositeOperation(vg, compositeOp);
  442. if (doFill)
  443. {
  444. nvgFillColor(vg, fillColor);
  445. nvgFill(vg);
  446. }
  447. nvgStrokeColor(vg, lineColor);
  448. nvgStrokeWidth(vg, lineThickness);
  449. nvgStroke(vg);
  450. nvgResetScissor(vg);
  451. nvgRestore(vg);
  452. nvgGlobalCompositeOperation(vg, NVG_SOURCE_OVER); // Restore to normal
  453. return;
  454. } // end drawWaveform()
  455. RACK_PLUGIN_MODEL_INIT(trowaSoft, MultiScope) {
  456. Model *modelMultiScope = Model::create<multiScope, multiScopeWidget>(/*manufacturer*/ TROWA_PLUGIN_NAME, /*slug*/ "multiScope", /*name*/ "multiScope", /*Tags*/ VISUAL_TAG, UTILITY_TAG);
  457. return modelMultiScope;
  458. }
  459. #endif // end if use new scope