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.

564 lines
17KB

  1. #include <string.h>
  2. #include <complex.h>
  3. #include <math.h>
  4. #include <float.h>
  5. #include "../LindenbergResearch.hpp"
  6. #include "../LRModel.hpp"
  7. #include "../dsp/kiss_fft/kiss_fft.h"
  8. namespace rack_plugin_LindenbergResearch {
  9. /*
  10. * FFT SCOPE, BASED ON SCOPE
  11. * Todo: precise f0 estimate
  12. */
  13. #define BUFFER_SIZE 8192
  14. #define FFT_POINTS BUFFER_SIZE
  15. #define FFT_POINTS_NYQ FFT_POINTS/2+1
  16. #define DIR_FFT 0
  17. #define INV_FFT 1
  18. #define FOFFS_RANGE 1.0
  19. #define ZOOM_RANGE 8.0
  20. using namespace rack;
  21. using namespace lrt;
  22. float cabsf_LG(kiss_fft_cpx v) {
  23. return sqrtf((v.r * v.r + v.i * v.i));
  24. }
  25. void HannWindow(float *w, int size) {
  26. if (size <= 0) return;
  27. if (w == NULL) {
  28. w = (float *) malloc(size * sizeof(float));
  29. }
  30. for (int i = 0; i < size; i++) {
  31. w[i] = 0.5 * (1 - cos(2 * M_PI * i / (size - 1))); // maybe double is better?
  32. }
  33. }
  34. struct Speck : LRModule {
  35. enum ParamIds {
  36. SCALE_1_PARAM,
  37. POS_1_PARAM,
  38. SCALE_2_PARAM,
  39. POS_2_PARAM,
  40. ZOOM_PARAM, // was ZOOM
  41. LINLOG_PARAM, // was MODE
  42. FOFFS_PARAM, // was TRIG
  43. ONOFF_PARAM,
  44. NUM_PARAMS
  45. };
  46. enum InputIds {
  47. INPUT_1,
  48. INPUT_2,
  49. NUM_INPUTS
  50. };
  51. enum OutputIds {
  52. OUTPUT_1,
  53. OUTPUT_2,
  54. NUM_OUTPUTS
  55. };
  56. enum LightsIds {
  57. LIGHTS_0_LIN,
  58. LIGHTS_1_LOG,
  59. LIGHTS_2_ON,
  60. NUM_LIGHTS,
  61. };
  62. float buffer1[BUFFER_SIZE] = {};
  63. float buffer2[BUFFER_SIZE] = {};
  64. float FFT1[FFT_POINTS_NYQ] = {};
  65. float FFT2[FFT_POINTS_NYQ] = {};
  66. int bufferIndex = 0;
  67. float frameIndex = 0;
  68. bool forceOff = false;
  69. bool linLog = false; // lin = 0, log = 1
  70. bool powered = false;
  71. kiss_fft_cfg cfg_for_FFT, cfg_for_IFFT;
  72. float HannW[BUFFER_SIZE];
  73. Speck() : LRModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  74. cfg_for_FFT = kiss_fft_alloc(FFT_POINTS, DIR_FFT, 0, 0);
  75. cfg_for_IFFT = kiss_fft_alloc(FFT_POINTS, INV_FFT, 0, 0);
  76. HannWindow(&HannW[0], BUFFER_SIZE);
  77. }
  78. ~Speck();
  79. void step() override;
  80. json_t *toJson() override {
  81. json_t *rootJ = LRModule::toJson();
  82. json_object_set_new(rootJ, "linLog", json_integer((int) linLog));
  83. return rootJ;
  84. }
  85. void fromJson(json_t *rootJ) override {
  86. LRModule::fromJson(rootJ);
  87. json_t *sumJ = json_object_get(rootJ, "linLog");
  88. if (sumJ)
  89. linLog = json_integer_value(sumJ);
  90. }
  91. void reset() override {
  92. linLog = false;
  93. powered = false;
  94. }
  95. };
  96. Speck::~Speck() {
  97. free(cfg_for_FFT);
  98. free(cfg_for_IFFT);
  99. }
  100. void Speck::step() {
  101. int n;
  102. kiss_fft_cpx cBufIn[FFT_POINTS], cBufOut[FFT_POINTS];
  103. forceOff = params[ONOFF_PARAM].value == 1;
  104. if (inputs[INPUT_1].active || inputs[INPUT_2].active) {
  105. if (!powered && !forceOff) {
  106. powered = true;
  107. } else if (powered && forceOff) {
  108. powered = false;
  109. }
  110. } else {
  111. powered = false;
  112. forceOff = false;
  113. }
  114. lights[LIGHTS_2_ON].value = powered;
  115. linLog = params[LINLOG_PARAM].value != 0.0;
  116. lights[LIGHTS_0_LIN].value = linLog ? 0.f : 1.f;
  117. lights[LIGHTS_1_LOG].value = linLog ? 1.f : 0.f;
  118. // pass through
  119. if (outputs[OUTPUT_1].active) {
  120. outputs[OUTPUT_1].value = (inputs[INPUT_1].value);
  121. }
  122. if (outputs[OUTPUT_2].active) {
  123. outputs[OUTPUT_2].value = (inputs[INPUT_2].value);
  124. }
  125. /* power off */
  126. if (!powered) return;
  127. //float deltaTime = powf(2.0, -14.0); // this could be the NFFT in the future (if rounded to nearest 2^N)
  128. //int frameCount = (int)ceilf(deltaTime * engineGetSampleRate());
  129. int frameCount = 1;
  130. if (bufferIndex < BUFFER_SIZE) {
  131. if (++frameIndex > frameCount) {
  132. frameIndex = 0;
  133. buffer1[bufferIndex] = (inputs[INPUT_1].value);
  134. buffer2[bufferIndex] = (inputs[INPUT_2].value);
  135. bufferIndex++;
  136. }
  137. } else {
  138. for (n = 0; n < FFT_POINTS; n++) {
  139. cBufIn[n].r = HannW[n] * buffer1[n];
  140. cBufIn[n].i = 0.0;
  141. }
  142. kiss_fft(cfg_for_FFT, cBufIn, cBufOut);
  143. for (n = 0; n < FFT_POINTS_NYQ; n++) {
  144. FFT1[n] = logf(cabsf_LG(cBufOut[n]));
  145. }
  146. for (n = 0; n < FFT_POINTS; n++) {
  147. cBufIn[n].r = HannW[n] * buffer2[n];
  148. cBufIn[n].i = 0.0;
  149. }
  150. kiss_fft(cfg_for_FFT, cBufIn, cBufOut);
  151. for (n = 0; n < FFT_POINTS_NYQ; n++) {
  152. FFT2[n] = logf(cabsf_LG(cBufOut[n]));
  153. }
  154. bufferIndex = 0;
  155. frameIndex = 0; // reset all. remove for future overlaps
  156. }
  157. /*
  158. // Reset buffer
  159. if (bufferIndex >= BUFFER_SIZE) {
  160. bufferIndex = 0; frameIndex = 0; return;
  161. }
  162. */
  163. }
  164. struct SpeckDisplay : TransparentWidget {
  165. Speck *module;
  166. int frame = 0;
  167. std::shared_ptr<Font> font;
  168. struct Stats {
  169. float f0, peakx, peaky;
  170. void calculate(float *values) {
  171. f0 = 0.0;
  172. peakx = 0.0;
  173. peaky = 0.0;
  174. for (int i = 0; i < FFT_POINTS_NYQ; i++) {
  175. float v = values[i];
  176. if (v > peaky) {
  177. peaky = v;
  178. peakx = i;
  179. }
  180. }
  181. peakx = engineGetSampleRate() / 2.0f * ((peakx) / (float) (FFT_POINTS_NYQ));
  182. f0 = peakx;
  183. }
  184. };
  185. Stats stats1, stats2;
  186. SpeckDisplay() {
  187. font = Font::load(assetPlugin(plugin, "res/ibm-plex-mono/IBMPlexMono-Medium.ttf"));
  188. }
  189. #define LOG_LOWER_FREQ 10.0 // lowest freq we are going to show in log mode
  190. float drawWaveform(NVGcontext *vg, float *values, float gain, float offset, float fzoom, float foffs, bool linLog) {
  191. int xpos;
  192. float nyq = engineGetSampleRate() / 2.0;
  193. float logMax = log10(nyq);
  194. float semilogx[FFT_POINTS_NYQ];
  195. float vgrid[100];
  196. float negOffs;
  197. int maxj = 0;
  198. Vec p;
  199. nvgSave(vg);
  200. Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15 * 2)));
  201. nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  202. nvgBeginPath(vg);
  203. // Draw maximum display left to right
  204. int lwp = 0; // lowest point to show
  205. int spacing = (nyq / FFT_POINTS_NYQ);
  206. for (lwp = 0; lwp < FFT_POINTS_NYQ; lwp++) {
  207. if (lwp * spacing > LOG_LOWER_FREQ) break;
  208. }
  209. // create the semilogx axis
  210. if (linLog) {
  211. vgrid[0] = log10(LOG_LOWER_FREQ);
  212. vgrid[0] = (vgrid[0] * b.size.x / logMax);
  213. int j = 1;
  214. // create lin grid values
  215. for (int f = 100; f < 1000; f += 100) {
  216. vgrid[j++] = f;
  217. }
  218. for (int f = 1000; f < nyq; f += 1000) {
  219. vgrid[j++] = f;
  220. }
  221. maxj = j;
  222. for (int i = 0; i < maxj; i++) {
  223. vgrid[i] = log10((float) (vgrid[i]));
  224. vgrid[i] = (vgrid[i] * ((float) (b.size.x) + vgrid[0]) / logMax);
  225. }
  226. semilogx[lwp] = log10((float) (lwp) * nyq / (float) FFT_POINTS_NYQ);
  227. semilogx[lwp] = (semilogx[lwp] * b.size.x / logMax); // apply the range of the box
  228. for (int i = lwp + 1; i < FFT_POINTS_NYQ; i++) {
  229. semilogx[i] = log10((float) (i) * nyq / (float) FFT_POINTS_NYQ);
  230. semilogx[i] = (b.size.x + semilogx[lwp] + 60) * semilogx[i] / logMax; // apply the range of the box
  231. }
  232. float residual = semilogx[FFT_POINTS_NYQ - 1] - (semilogx[FFT_POINTS_NYQ - 1] / fzoom); // excluded from plot
  233. negOffs = -(0.8 * foffs / FOFFS_RANGE) * residual;
  234. /*
  235. for (int i = 0; i < FFT_POINTS_NYQ; i++) {
  236. semilogx[i] = negOffs + semilogx[i]; // apply the range of the box TODO togliere?
  237. }
  238. for (int j = 0; j < maxj; j++) {
  239. vgrid[j] += negOffs;
  240. }
  241. */
  242. for (int i = lwp; i < FFT_POINTS_NYQ; i++) {
  243. float value = values[i] * gain + offset;
  244. p = Vec(b.pos.x + fzoom * (((semilogx[i]) - semilogx[lwp]) + negOffs), b.pos.y + b.size.y * (1 - value) / 2);
  245. if (i <= lwp)
  246. nvgMoveTo(vg, p.x, p.y);
  247. else
  248. nvgLineTo(vg, p.x, p.y);
  249. }
  250. } else {
  251. int zoomPoints = floor((float) (FFT_POINTS_NYQ) / (fzoom < 1.0 ? 1.0 : fzoom));
  252. int fstart = floor(foffs * ((float) (FFT_POINTS_NYQ) - (float) (zoomPoints)));
  253. for (int i = 0; i < zoomPoints; i++) {
  254. float value = values[i + fstart] * gain + offset;
  255. xpos = i;
  256. p = Vec(b.pos.x + xpos * b.size.x / (zoomPoints/*FFT_POINTS_NYQ*/- 1), b.pos.y + b.size.y * (1 - value) / 2);
  257. if (i == 0)
  258. nvgMoveTo(vg, p.x, p.y);
  259. else
  260. nvgLineTo(vg, p.x, p.y);
  261. }
  262. }
  263. //printf("xpos %d, bsize %f, zoomPts %d, bpos %f, x %f\n", xpos, b.size.x, zoomPoints, b.pos.x, p.x);
  264. nvgLineCap(vg, NVG_ROUND);
  265. nvgMiterLimit(vg, 2.0);
  266. nvgStrokeWidth(vg, 1.75);
  267. nvgGlobalCompositeOperation(vg, NVG_LIGHTER);
  268. nvgStroke(vg);
  269. nvgResetScissor(vg);
  270. nvgRestore(vg);
  271. if (linLog) {
  272. // UP TO 1k
  273. for (int j = 0; j < maxj; j++) {
  274. Vec p = Vec(b.pos.x + fzoom * (vgrid[j] - vgrid[0] + negOffs), box.size.y);
  275. nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x10));
  276. {
  277. nvgBeginPath(vg);
  278. nvgMoveTo(vg, p.x, p.y);
  279. nvgLineTo(vg, p.x, 0);
  280. nvgClosePath(vg);
  281. }
  282. nvgStroke(vg);
  283. }
  284. }
  285. return negOffs;
  286. }
  287. #define VERT_GRID_DIST 20
  288. #define HORZ_GRID_DIST 20
  289. void drawGrid(NVGcontext *vg, float fzoom, float foffs, bool linLog, float negOffs) {
  290. Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15 * 2)));
  291. nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  292. float nyq = engineGetSampleRate() / 2.0;
  293. float range = nyq / (fzoom < 1.0 ? 1.0 : fzoom);
  294. float fstart = foffs * (nyq - range);
  295. int first = ceil(fstart / 1000) * 1000;
  296. float diff = first - fstart;
  297. // VERT LINES
  298. if (linLog == 0) {
  299. for (int f = first; f < first + range; f += 1000) {
  300. float v = ((f - first + diff) / range) * box.size.x;
  301. Vec p = Vec(v, box.size.y);
  302. nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x10));
  303. {
  304. nvgBeginPath(vg);
  305. nvgMoveTo(vg, p.x, p.y);
  306. nvgLineTo(vg, p.x, 0);
  307. nvgClosePath(vg);
  308. }
  309. nvgStroke(vg);
  310. }
  311. } else { ; } // is done in the drawWaveform for convenience
  312. // HORZ LINES
  313. for (int h = 0; h < box.size.y; h += HORZ_GRID_DIST) {
  314. Vec p = Vec(box.size.x, h);
  315. nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x10));
  316. {
  317. nvgBeginPath(vg);
  318. nvgMoveTo(vg, p.x, p.y);
  319. nvgLineTo(vg, 0, p.y);
  320. nvgClosePath(vg);
  321. }
  322. nvgStroke(vg);
  323. }
  324. }
  325. /* void drawTrig(NVGcontext *vg, float value, float gain, float offset) {
  326. Rect b = Rect(Vec(0, 15), box.size.minus(Vec(0, 15*2)));
  327. nvgScissor(vg, b.pos.x, b.pos.y, b.size.x, b.size.y);
  328. value = value * gain + offset;
  329. Vec p = Vec(box.size.x, b.pos.y + b.size.y * (1 - value) / 2);
  330. // Draw line
  331. nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x10));
  332. {
  333. nvgBeginPath(vg);
  334. nvgMoveTo(vg, p.x - 13, p.y);
  335. nvgLineTo(vg, 0, p.y);
  336. nvgClosePath(vg);
  337. }
  338. nvgStroke(vg);
  339. // Draw indicator
  340. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0x60));
  341. {
  342. nvgBeginPath(vg);
  343. nvgMoveTo(vg, p.x - 2, p.y - 4);
  344. nvgLineTo(vg, p.x - 9, p.y - 4);
  345. nvgLineTo(vg, p.x - 13, p.y);
  346. nvgLineTo(vg, p.x - 9, p.y + 4);
  347. nvgLineTo(vg, p.x - 2, p.y + 4);
  348. nvgClosePath(vg);
  349. }
  350. nvgFill(vg);
  351. nvgFontSize(vg, 8);
  352. nvgFontFaceId(vg, font->handle);
  353. nvgFillColor(vg, nvgRGBA(0x1e, 0x28, 0x2b, 0xff));
  354. nvgText(vg, p.x - 8, p.y + 3, "T", NULL);
  355. nvgResetScissor(vg);
  356. }*/
  357. void drawStats(NVGcontext *vg, Vec pos, const char *title, Stats *stats) {
  358. nvgFontSize(vg, 12);
  359. nvgFontFaceId(vg, font->handle);
  360. nvgTextLetterSpacing(vg, 0);
  361. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0xff));
  362. nvgText(vg, pos.x + 5, pos.y + 10, title, NULL);
  363. nvgFillColor(vg, nvgRGBA(0x0f, 0xff, 0xff, 0xA8));
  364. char text[128];
  365. snprintf(text, sizeof(text), /*"F0 %5.2f */"PeakX %5.1f PeakY % 5.1f", /*stats->f0, */stats->peakx, stats->peaky);
  366. //printf("%s\n", text);
  367. nvgText(vg, pos.x + 27, pos.y + 10, text, NULL);
  368. }
  369. void draw(NVGcontext *vg) override {
  370. float gain1 = powf(2.0, roundf(module->params[Speck::SCALE_1_PARAM].value)) / 12.0;
  371. float gain2 = powf(2.0, roundf(module->params[Speck::SCALE_2_PARAM].value)) / 12.0;
  372. float pos1 = module->params[Speck::POS_1_PARAM].value;
  373. float pos2 = module->params[Speck::POS_2_PARAM].value;
  374. float zoom = module->params[Speck::ZOOM_PARAM].value;
  375. float freqOffs = module->params[Speck::FOFFS_PARAM].value;
  376. float negOffs;
  377. // Draw waveforms
  378. // Y
  379. if (module->inputs[Speck::INPUT_2].active) {
  380. nvgStrokeColor(vg, nvgRGBAf(0.9f, 0.3f, 0.1, 1.f));
  381. //drawWaveform(vg, module->buffer2, gain2, pos2);
  382. negOffs = drawWaveform(vg, module->FFT2, gain2, pos2, zoom, freqOffs, module->linLog);
  383. }
  384. // X
  385. if (module->inputs[Speck::INPUT_1].active) {
  386. nvgStrokeColor(vg, nvgRGBAf(0.f, 0.3f, 0.8, 1.f));
  387. //drawWaveform(vg, module->buffer1, gain1, pos1);
  388. negOffs = drawWaveform(vg, module->FFT1, gain1, pos1, zoom, freqOffs, module->linLog);
  389. }
  390. //drawTrig(vg, module->params[Speck::TRIG_PARAM], gain1, pos1);
  391. // Calculate and draw stats
  392. if (++frame >= 4) {
  393. frame = 0;
  394. stats1.calculate(module->FFT1);
  395. stats2.calculate(module->FFT2);
  396. }
  397. drawStats(vg, Vec(0, 4), "IN1", &stats1);
  398. drawStats(vg, Vec(0, box.size.y - 15), "IN2", &stats2);
  399. drawGrid(vg, zoom, freqOffs, module->linLog, negOffs);
  400. }
  401. };
  402. struct SpeckWidget : LRModuleWidget {
  403. SpeckWidget(Speck *module);
  404. };
  405. SpeckWidget::SpeckWidget(Speck *module) : LRModuleWidget(module) {
  406. panel->addSVGVariant(LRGestalt::DARK, SVG::load(assetPlugin(plugin, "res/panels/SpeckAnalyzer.svg")));
  407. // panel->addSVGVariant(SVG::load(assetPlugin(plugin, "res/panels/SpeckAnalyzer.svg")));
  408. // panel->addSVGVariant(SVG::load(assetPlugin(plugin, "res/panels/SpeckAnalyzer.svg")));
  409. noVariants = true;
  410. panel->init();
  411. addChild(panel);
  412. box.size = panel->box.size;
  413. addChild(Widget::create<ScrewLight>(Vec(15, 1)));
  414. addChild(Widget::create<ScrewLight>(Vec(300 - 30, 1)));
  415. addChild(Widget::create<ScrewLight>(Vec(15, 364)));
  416. addChild(Widget::create<ScrewLight>(Vec(300 - 30, 364)));
  417. addChild(Widget::create<ScrewLight>(Vec(box.size.x - 30, 1)));
  418. addChild(Widget::create<ScrewLight>(Vec(box.size.x - 30, 366)));
  419. SpeckDisplay *display = new SpeckDisplay();
  420. display->module = module;
  421. display->box.pos = Vec(300, 0);
  422. display->box.size = Vec(box.size.x - 300, 380);
  423. addChild(display);
  424. addParam(ParamWidget::create<LRSwitch>(Vec(33, 340), module, Speck::ONOFF_PARAM, 0.0, 1.0, 0.0));
  425. addParam(ParamWidget::create<LRAlternateMiddleLight>(Vec(118 - 50, 124), module, Speck::SCALE_1_PARAM, -10.0f, 20.0, -1.0f));
  426. addParam(ParamWidget::create<LRAlternateMiddleLight>(Vec(118 - 50, 177), module, Speck::POS_1_PARAM, -1.0f, 1.0, 0.0));
  427. addParam(ParamWidget::create<LRAlternateMiddleLight>(Vec(197 - 50, 124), module, Speck::SCALE_2_PARAM, -10.0f, 20.0, -1.0f));
  428. addParam(ParamWidget::create<LRAlternateMiddleLight>(Vec(197 - 50, 177), module, Speck::POS_2_PARAM, -1.0f, 1.0, 0.0));
  429. addParam(ParamWidget::create<LRAlternateMiddleLight>(Vec(253 - 50, 124), module, Speck::ZOOM_PARAM, 1.0, ZOOM_RANGE, 1.0));
  430. addParam(ParamWidget::create<LRAlternateMiddleLight>(Vec(253 - 50, 177), module, Speck::FOFFS_PARAM, 0.0, FOFFS_RANGE, 0.0));
  431. addParam(ParamWidget::create<LRSwitch>(Vec(258, 244), module, Speck::LINLOG_PARAM, 0.0, 1.0, 0.0));
  432. addInput(Port::create<LRIOPortBLight>(Vec(12, 240), Port::INPUT, module, Speck::INPUT_1));
  433. addInput(Port::create<LRIOPortBLight>(Vec(59, 240), Port::INPUT, module, Speck::INPUT_2));
  434. addOutput(Port::create<LRIOPortBLight>(Vec(9, 306), Port::OUTPUT, module, Speck::OUTPUT_1));
  435. addOutput(Port::create<LRIOPortBLight>(Vec(56, 306), Port::OUTPUT, module, Speck::OUTPUT_2));
  436. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(286, 230), module, Speck::LIGHTS_0_LIN));
  437. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(286, 280), module, Speck::LIGHTS_1_LOG));
  438. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(Vec(265, 30), module, Speck::LIGHTS_2_ON));
  439. }
  440. } // namespace rack_plugin_LindenbergResearch
  441. using namespace rack_plugin_LindenbergResearch;
  442. RACK_PLUGIN_MODEL_INIT(LindenbergResearch, Speck) {
  443. Model *modelSpeck = Model::create<Speck, SpeckWidget>("Lindenberg Research", "Speck", "Spectrum Analyzer", VISUAL_TAG, UTILITY_TAG);
  444. return modelSpeck;
  445. }