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.

558 lines
16KB

  1. #include "global_pre.hpp"
  2. #include "Analyzer.hpp"
  3. #include "dsp/signal.hpp"
  4. #include "global_ui.hpp"
  5. struct bogaudio::ChannelAnalyzer : SpectrumAnalyzer {
  6. int _binsN;
  7. float* _bins;
  8. AveragingBuffer<float>* _averagedBins;
  9. ChannelAnalyzer(
  10. SpectrumAnalyzer::Size size,
  11. SpectrumAnalyzer::Overlap overlap,
  12. SpectrumAnalyzer::WindowType windowType,
  13. float sampleRate,
  14. int averageN,
  15. int binSize
  16. )
  17. : SpectrumAnalyzer(size, overlap, windowType, sampleRate)
  18. , _binsN(size / binSize)
  19. , _bins(averageN == 1 ? new float[_binsN] {} : NULL)
  20. , _averagedBins(averageN == 1 ? NULL : new AveragingBuffer<float>(_binsN, averageN))
  21. {
  22. assert(averageN >= 1);
  23. assert(binSize >= 1);
  24. }
  25. virtual ~ChannelAnalyzer() {
  26. if (_bins) {
  27. delete[] _bins;
  28. }
  29. if (_averagedBins) {
  30. delete _averagedBins;
  31. }
  32. }
  33. const float* getBins() {
  34. if (_bins) {
  35. return _bins;
  36. }
  37. return _averagedBins->getAverages();
  38. }
  39. bool step(float sample) override;
  40. float getPeak();
  41. };
  42. bool ChannelAnalyzer::step(float sample) {
  43. if (SpectrumAnalyzer::step(sample)) {
  44. if (_bins) {
  45. getMagnitudes(_bins, _binsN);
  46. }
  47. else {
  48. float* frame = _averagedBins->getInputFrame();
  49. getMagnitudes(frame, _binsN);
  50. _averagedBins->commitInputFrame();
  51. }
  52. return true;
  53. }
  54. return false;
  55. }
  56. float ChannelAnalyzer::getPeak() {
  57. float max = 0.0;
  58. float sum = 0.0;
  59. int maxBin = 0;
  60. const float* bins = getBins();
  61. for (int bin = 0; bin < _binsN; ++bin) {
  62. if (bins[bin] > max) {
  63. max = bins[bin];
  64. maxBin = bin;
  65. }
  66. sum += bins[bin];
  67. }
  68. const int bandsPerBin = _size / _binsN;
  69. const float fWidth = (_sampleRate / 2.0f) / (float)(_size / bandsPerBin);
  70. return (maxBin + 0.5f)*fWidth;
  71. }
  72. void Analyzer::onReset() {
  73. resetChannels();
  74. }
  75. void Analyzer::onSampleRateChange() {
  76. resetChannels();
  77. }
  78. void Analyzer::resetChannels() {
  79. if (_channelA) {
  80. delete _channelA;
  81. _channelA = NULL;
  82. }
  83. if (_channelB) {
  84. delete _channelB;
  85. _channelB = NULL;
  86. }
  87. if (_channelC) {
  88. delete _channelC;
  89. _channelC = NULL;
  90. }
  91. if (_channelD) {
  92. delete _channelD;
  93. _channelD = NULL;
  94. }
  95. }
  96. SpectrumAnalyzer::Size Analyzer::size() {
  97. if (_quality == QUALITY_HIGH) {
  98. return SpectrumAnalyzer::SIZE_4096;
  99. }
  100. return SpectrumAnalyzer::SIZE_1024;
  101. }
  102. void Analyzer::step() {
  103. _range = params[RANGE_PARAM].value;
  104. const float maxTime = 0.5;
  105. float smooth = params[SMOOTH_PARAM].value * maxTime;
  106. smooth /= size() / (_overlap * engineGetSampleRate());
  107. int smoothN = std::max(1, (int)roundf(smooth));
  108. if (_averageN != smoothN) {
  109. _averageN = smoothN;
  110. resetChannels();
  111. }
  112. Quality quality = params[QUALITY_PARAM].value > 1.5 ? QUALITY_HIGH : QUALITY_GOOD;
  113. if (_quality != quality) {
  114. _quality = quality;
  115. resetChannels();
  116. }
  117. _running = params[POWER_PARAM].value == 1.0;
  118. stepChannel(_channelA, _running, inputs[SIGNALA_INPUT], outputs[SIGNALA_OUTPUT]);
  119. stepChannel(_channelB, _running, inputs[SIGNALB_INPUT], outputs[SIGNALB_OUTPUT]);
  120. stepChannel(_channelC, _running, inputs[SIGNALC_INPUT], outputs[SIGNALC_OUTPUT]);
  121. stepChannel(_channelD, _running, inputs[SIGNALD_INPUT], outputs[SIGNALD_OUTPUT]);
  122. lights[QUALITY_HIGH_LIGHT].value = _running && quality == QUALITY_HIGH;
  123. lights[QUALITY_GOOD_LIGHT].value = _running && quality == QUALITY_GOOD;
  124. lights[POWER_ON_LIGHT].value = _running;
  125. }
  126. void Analyzer::stepChannel(ChannelAnalyzer*& channelPointer, bool running, Input& input, Output& output) {
  127. if (running && input.active) {
  128. if (!channelPointer) {
  129. channelPointer = new ChannelAnalyzer(
  130. size(),
  131. _overlap,
  132. SpectrumAnalyzer::WINDOW_HAMMING,
  133. engineGetSampleRate(),
  134. _averageN,
  135. _binAverageN
  136. );
  137. }
  138. channelPointer->step(input.value);
  139. output.value = input.value;
  140. }
  141. else {
  142. if (channelPointer) {
  143. delete channelPointer;
  144. channelPointer = NULL;
  145. }
  146. output.value = input.value;
  147. }
  148. }
  149. struct AnalyzerDisplay : TransparentWidget {
  150. const int _insetAround = 2;
  151. const int _insetLeft = _insetAround + 12;
  152. const int _insetRight = _insetAround + 2;
  153. const int _insetTop = _insetAround + 13;
  154. const int _insetBottom = _insetAround + 9;
  155. const float _displayDB = 80.0;
  156. const float _positiveDisplayDB = 20.0;
  157. const float xAxisLogFactor = 1 / 3.321; // magic number.
  158. const NVGcolor _axisColor = nvgRGBA(0xff, 0xff, 0xff, 0x70);
  159. const NVGcolor _textColor = nvgRGBA(0xff, 0xff, 0xff, 0xc0);
  160. const NVGcolor _channelAColor = nvgRGBA(0x00, 0xff, 0x00, 0xd0);
  161. const NVGcolor _channelBColor = nvgRGBA(0xff, 0x00, 0xff, 0xd0);
  162. const NVGcolor _channelCColor = nvgRGBA(0xff, 0x80, 0x00, 0xd0);
  163. const NVGcolor _channelDColor = nvgRGBA(0x00, 0x80, 0xff, 0xd0);
  164. Analyzer* _module;
  165. const Vec _size;
  166. const Vec _graphSize;
  167. std::shared_ptr<Font> _font;
  168. AnalyzerDisplay(
  169. Analyzer* module,
  170. Vec size
  171. )
  172. : _module(module)
  173. , _size(size)
  174. , _graphSize(_size.x - _insetLeft - _insetRight, _size.y - _insetTop - _insetBottom)
  175. , _font(Font::load(assetPlugin(plugin, "res/fonts/inconsolata.ttf")))
  176. {
  177. }
  178. void draw(NVGcontext* vg) override;
  179. void drawBackground(NVGcontext* vg);
  180. void drawHeader(NVGcontext* vg);
  181. void drawYAxis(NVGcontext* vg, float strokeWidth);
  182. void drawXAxis(NVGcontext* vg, float strokeWidth);
  183. void drawXAxisLine(NVGcontext* vg, float hz, float maxHz);
  184. void drawGraph(NVGcontext* vg, const float* bins, int binsN, NVGcolor color, float strokeWidth);
  185. void drawText(NVGcontext* vg, const char* s, float x, float y, float rotation = 0.0, const NVGcolor* color = NULL);
  186. int binValueToHeight(float value);
  187. };
  188. void AnalyzerDisplay::draw(NVGcontext* vg) {
  189. drawBackground(vg);
  190. if (_module->_running) {
  191. #ifdef USE_VST2
  192. float strokeWidth = std::max(1.0f, 3 - rack::global_ui->app.gRackScene->zoomWidget->zoom);
  193. #else
  194. float strokeWidth = std::max(1.0f, 3 - gRackScene->zoomWidget->zoom);
  195. #endif // USE_VST2
  196. nvgSave(vg);
  197. nvgScissor(vg, _insetAround, _insetAround, _size.x - _insetAround, _size.y - _insetAround);
  198. drawHeader(vg);
  199. drawYAxis(vg, strokeWidth);
  200. drawXAxis(vg, strokeWidth);
  201. if (_module->_channelA) {
  202. drawGraph(vg, _module->_channelA->getBins(), _module->_channelA->_binsN, _channelAColor, strokeWidth);
  203. }
  204. if (_module->_channelB) {
  205. drawGraph(vg, _module->_channelB->getBins(), _module->_channelB->_binsN, _channelBColor, strokeWidth);
  206. }
  207. if (_module->_channelC) {
  208. drawGraph(vg, _module->_channelC->getBins(), _module->_channelC->_binsN, _channelCColor, strokeWidth);
  209. }
  210. if (_module->_channelD) {
  211. drawGraph(vg, _module->_channelD->getBins(), _module->_channelD->_binsN, _channelDColor, strokeWidth);
  212. }
  213. nvgRestore(vg);
  214. }
  215. }
  216. void AnalyzerDisplay::drawBackground(NVGcontext* vg) {
  217. nvgSave(vg);
  218. nvgBeginPath(vg);
  219. nvgRect(vg, 0, 0, _size.x, _size.y);
  220. nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
  221. nvgFill(vg);
  222. nvgStrokeColor(vg, nvgRGBA(0xc0, 0xc0, 0xc0, 0xff));
  223. nvgStroke(vg);
  224. nvgRestore(vg);
  225. }
  226. void AnalyzerDisplay::drawHeader(NVGcontext* vg) {
  227. nvgSave(vg);
  228. const int textY = -4;
  229. const int charPx = 5;
  230. const int sLen = 100;
  231. char s[sLen];
  232. int x = _insetAround + 2;
  233. int n = snprintf(s, sLen, "Peaks (+/-%0.1f):", (engineGetSampleRate() / 2.0f) / (float)(_module->size() / _module->_binAverageN));
  234. drawText(vg, s, x, _insetTop + textY);
  235. x += n * charPx - 0;
  236. if (_module->_channelA) {
  237. snprintf(s, sLen, "A:%7.1f", _module->_channelA->getPeak());
  238. drawText(vg, s, x, _insetTop + textY, 0.0, &_channelAColor);
  239. }
  240. x += 9 * charPx + 3;
  241. if (_module->_channelB) {
  242. snprintf(s, sLen, "B:%7.1f", _module->_channelB->getPeak());
  243. drawText(vg, s, x, _insetTop + textY, 0.0, &_channelBColor);
  244. }
  245. x += 9 * charPx + 3;
  246. if (_module->_channelC) {
  247. snprintf(s, sLen, "C:%7.1f", _module->_channelC->getPeak());
  248. drawText(vg, s, x, _insetTop + textY, 0.0, &_channelCColor);
  249. }
  250. x += 9 * charPx + 3;
  251. if (_module->_channelD) {
  252. snprintf(s, sLen, "D:%7.1f", _module->_channelD->getPeak());
  253. drawText(vg, s, x, _insetTop + textY, 0.0, &_channelDColor);
  254. }
  255. nvgRestore(vg);
  256. }
  257. void AnalyzerDisplay::drawYAxis(NVGcontext* vg, float strokeWidth) {
  258. nvgSave(vg);
  259. nvgStrokeColor(vg, _axisColor);
  260. nvgStrokeWidth(vg, strokeWidth);
  261. const int lineX = _insetLeft - 2;
  262. const int textX = 9;
  263. const float textR = -M_PI/2.0;
  264. nvgBeginPath(vg);
  265. int lineY = _insetTop;
  266. nvgMoveTo(vg, lineX, lineY);
  267. nvgLineTo(vg, _size.x - _insetRight, lineY);
  268. nvgStroke(vg);
  269. nvgBeginPath(vg);
  270. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB + 12.0)/_displayDB);
  271. nvgMoveTo(vg, lineX, lineY);
  272. nvgLineTo(vg, _size.x - _insetRight, lineY);
  273. nvgStroke(vg);
  274. drawText(vg, "12", textX, lineY + 5.0, textR);
  275. nvgBeginPath(vg);
  276. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB)/_displayDB);
  277. nvgMoveTo(vg, lineX, lineY);
  278. nvgLineTo(vg, _size.x - _insetRight, lineY);
  279. nvgStrokeWidth(vg, strokeWidth * 1.5);
  280. nvgStroke(vg);
  281. nvgStrokeWidth(vg, strokeWidth);
  282. drawText(vg, "0", textX, lineY + 2.3, textR);
  283. nvgBeginPath(vg);
  284. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB - 12.0)/_displayDB);
  285. nvgMoveTo(vg, lineX, lineY);
  286. nvgLineTo(vg, _size.x - _insetRight, lineY);
  287. nvgStroke(vg);
  288. drawText(vg, "-12", textX, lineY + 10, textR);
  289. nvgBeginPath(vg);
  290. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB - 24.0)/_displayDB);
  291. nvgMoveTo(vg, lineX, lineY);
  292. nvgLineTo(vg, _size.x - _insetRight, lineY);
  293. nvgStroke(vg);
  294. drawText(vg, "-24", textX, lineY + 10, textR);
  295. nvgBeginPath(vg);
  296. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB - 48.0)/_displayDB);
  297. nvgMoveTo(vg, lineX, lineY);
  298. nvgLineTo(vg, _size.x - _insetRight, lineY);
  299. nvgStroke(vg);
  300. drawText(vg, "-48", textX, lineY + 10, textR);
  301. nvgBeginPath(vg);
  302. lineY = _insetTop + _graphSize.y + 1;
  303. nvgMoveTo(vg, lineX, lineY);
  304. nvgLineTo(vg, _size.x - _insetRight, lineY);
  305. nvgStroke(vg);
  306. nvgBeginPath(vg);
  307. nvgMoveTo(vg, lineX, _insetTop);
  308. nvgLineTo(vg, lineX, lineY);
  309. nvgStroke(vg);
  310. drawText(vg, "dB", textX, _size.y - _insetBottom, textR);
  311. nvgRestore(vg);
  312. }
  313. void AnalyzerDisplay::drawXAxis(NVGcontext* vg, float strokeWidth) {
  314. nvgSave(vg);
  315. nvgStrokeColor(vg, _axisColor);
  316. nvgStrokeWidth(vg, strokeWidth);
  317. const float maxHz = _module->_range * (engineGetSampleRate() / 2.0);
  318. float hz = 100.0;
  319. while (hz < maxHz && hz < 1001.0) {
  320. drawXAxisLine(vg, hz, maxHz);
  321. hz += 100.0;
  322. }
  323. hz = 2000.0;
  324. while (hz < maxHz && hz < 10001.0) {
  325. drawXAxisLine(vg, hz, maxHz);
  326. hz += 1000.0;
  327. }
  328. hz = 20000.0;
  329. while (hz < maxHz && hz < 100001.0) {
  330. drawXAxisLine(vg, hz, maxHz);
  331. hz += 10000.0;
  332. }
  333. drawText(vg, "Hz", _insetLeft, _size.y - 2);
  334. {
  335. float x = 100.0 / maxHz;
  336. x = powf(x, xAxisLogFactor);
  337. if (x < 1.0) {
  338. x *= _graphSize.x;
  339. drawText(vg, "100", _insetLeft + x - 8, _size.y - 2);
  340. }
  341. }
  342. {
  343. float x = 1000.0 / maxHz;
  344. x = powf(x, xAxisLogFactor);
  345. if (x < 1.0) {
  346. x *= _graphSize.x;
  347. drawText(vg, "1k", _insetLeft + x - 4, _size.y - 2);
  348. }
  349. }
  350. {
  351. float x = 10000.0 / maxHz;
  352. x = powf(x, xAxisLogFactor);
  353. if (x < 1.0) {
  354. x *= _graphSize.x;
  355. drawText(vg, "10k", _insetLeft + x - 7, _size.y - 2);
  356. }
  357. }
  358. nvgRestore(vg);
  359. }
  360. void AnalyzerDisplay::drawXAxisLine(NVGcontext* vg, float hz, float maxHz) {
  361. float x = hz / maxHz;
  362. x = powf(x, xAxisLogFactor);
  363. if (x < 1.0) {
  364. x *= _graphSize.x;
  365. nvgBeginPath(vg);
  366. nvgMoveTo(vg, _insetLeft + x, _insetTop);
  367. nvgLineTo(vg, _insetLeft + x, _insetTop + _graphSize.y);
  368. nvgStroke(vg);
  369. }
  370. }
  371. void AnalyzerDisplay::drawGraph(NVGcontext* vg, const float* bins, int binsN, NVGcolor color, float strokeWidth) {
  372. const int pointsN = roundf(_module->_range*(_module->size()/2));
  373. nvgSave(vg);
  374. nvgScissor(vg, _insetLeft, _insetTop, _graphSize.x, _graphSize.y);
  375. nvgStrokeColor(vg, color);
  376. nvgStrokeWidth(vg, strokeWidth);
  377. nvgBeginPath(vg);
  378. for (int i = 0; i < pointsN; ++i) {
  379. int height = binValueToHeight(bins[i]);
  380. if (i == 0) {
  381. nvgMoveTo(vg, _insetLeft, _insetTop + (_graphSize.y - height));
  382. }
  383. else {
  384. float x = _graphSize.x * powf(i / (float)pointsN, xAxisLogFactor);
  385. nvgLineTo(vg, _insetLeft + x, _insetTop + (_graphSize.y - height));
  386. }
  387. }
  388. nvgStroke(vg);
  389. nvgRestore(vg);
  390. }
  391. void AnalyzerDisplay::drawText(NVGcontext* vg, const char* s, float x, float y, float rotation, const NVGcolor* color) {
  392. nvgSave(vg);
  393. nvgTranslate(vg, x, y);
  394. nvgRotate(vg, rotation);
  395. nvgFontSize(vg, 10);
  396. nvgFontFaceId(vg, _font->handle);
  397. nvgFillColor(vg, color ? *color : _textColor);
  398. nvgText(vg, 0, 0, s, NULL);
  399. nvgRestore(vg);
  400. }
  401. int AnalyzerDisplay::binValueToHeight(float value) {
  402. const float minDB = -(_displayDB - _positiveDisplayDB);
  403. if (value < 0.00001f) {
  404. return 0;
  405. }
  406. value /= 10.0f; // arbitrarily use 5.0f as reference "maximum" baseline signal (e.g. raw output of an oscillator)...but signals are +/-5, so 10 total.
  407. value = powf(value, 0.5f); // undoing magnitude scaling of levels back from the FFT?
  408. value = amplitudeToDecibels(value);
  409. value = std::max(minDB, value);
  410. value = std::min(_positiveDisplayDB, value);
  411. value -= minDB;
  412. value /= _displayDB;
  413. return roundf(_graphSize.y * value);
  414. }
  415. struct OneTenKnob : Knob38 {
  416. OneTenKnob() : Knob38() {
  417. minAngle = -0.664*M_PI;
  418. }
  419. };
  420. struct AnalyzerWidget : ModuleWidget {
  421. static constexpr int hp = 20;
  422. AnalyzerWidget(Analyzer* module) : ModuleWidget(module) {
  423. box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT);
  424. {
  425. auto panel = new SVGPanel();
  426. panel->box.size = box.size;
  427. panel->setBackground(SVG::load(assetPlugin(plugin, "res/Analyzer.svg")));
  428. addChild(panel);
  429. }
  430. {
  431. auto inset = Vec(10, 25);
  432. auto size = Vec(box.size.x - 2*inset.x, 230);
  433. auto display = new AnalyzerDisplay(module, size);
  434. display->box.pos = inset;
  435. display->box.size = size;
  436. addChild(display);
  437. }
  438. addChild(Widget::create<ScrewSilver>(Vec(15, 0)));
  439. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0)));
  440. addChild(Widget::create<ScrewSilver>(Vec(15, 365)));
  441. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365)));
  442. // generated by svg_widgets.rb
  443. auto rangeParamPosition = Vec(35.08, 271.08);
  444. auto smoothParamPosition = Vec(109.08, 271.08);
  445. auto qualityParamPosition = Vec(188.02, 300.02);
  446. auto powerParamPosition = Vec(261.02, 300.02);
  447. auto signalaInputPosition = Vec(13.5, 323.0);
  448. auto signalbInputPosition = Vec(86.5, 323.0);
  449. auto signalcInputPosition = Vec(160.5, 323.0);
  450. auto signaldInputPosition = Vec(233.5, 323.0);
  451. auto signalaOutputPosition = Vec(42.5, 323.0);
  452. auto signalbOutputPosition = Vec(115.5, 323.0);
  453. auto signalcOutputPosition = Vec(189.5, 323.0);
  454. auto signaldOutputPosition = Vec(262.5, 323.0);
  455. auto qualityHighLightPosition = Vec(179.0, 273.0);
  456. auto qualityGoodLightPosition = Vec(179.0, 288.0);
  457. auto powerOnLightPosition = Vec(252.0, 288.0);
  458. // end generated by svg_widgets.rb
  459. addParam(ParamWidget::create<OneTenKnob>(rangeParamPosition, module, Analyzer::RANGE_PARAM, 0.1, 1.0, 0.5));
  460. addParam(ParamWidget::create<Knob38>(smoothParamPosition, module, Analyzer::SMOOTH_PARAM, 0.0, 1.0, 0.5));
  461. addParam(ParamWidget::create<StatefulButton9>(qualityParamPosition, module, Analyzer::QUALITY_PARAM, 1.0, 2.0, 1.0));
  462. {
  463. auto w = ParamWidget::create<StatefulButton9>(powerParamPosition, module, Analyzer::POWER_PARAM, 0.0, 1.0, 1.0);
  464. w->randomizable = false;
  465. addParam(w);
  466. }
  467. addInput(Port::create<Port24>(signalaInputPosition, Port::INPUT, module, Analyzer::SIGNALA_INPUT));
  468. addInput(Port::create<Port24>(signalbInputPosition, Port::INPUT, module, Analyzer::SIGNALB_INPUT));
  469. addInput(Port::create<Port24>(signalcInputPosition, Port::INPUT, module, Analyzer::SIGNALC_INPUT));
  470. addInput(Port::create<Port24>(signaldInputPosition, Port::INPUT, module, Analyzer::SIGNALD_INPUT));
  471. addOutput(Port::create<Port24>(signalaOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALA_OUTPUT));
  472. addOutput(Port::create<Port24>(signalbOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALB_OUTPUT));
  473. addOutput(Port::create<Port24>(signalcOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALC_OUTPUT));
  474. addOutput(Port::create<Port24>(signaldOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALD_OUTPUT));
  475. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(qualityHighLightPosition, module, Analyzer::QUALITY_HIGH_LIGHT));
  476. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(qualityGoodLightPosition, module, Analyzer::QUALITY_GOOD_LIGHT));
  477. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(powerOnLightPosition, module, Analyzer::POWER_ON_LIGHT));
  478. }
  479. };
  480. RACK_PLUGIN_MODEL_INIT(Bogaudio, Analyzer) {
  481. Model *modelAnalyzer = createModel<Analyzer, AnalyzerWidget>("Bogaudio-Analyzer", "Analyzer", "spectrum analyzer", VISUAL_TAG);
  482. return modelAnalyzer;
  483. }