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.

554 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. float strokeWidth = std::max(1.0f, 3 - RACK_PLUGIN_UI_RACKSCENE->zoomWidget->zoom);
  192. nvgSave(vg);
  193. nvgScissor(vg, _insetAround, _insetAround, _size.x - _insetAround, _size.y - _insetAround);
  194. drawHeader(vg);
  195. drawYAxis(vg, strokeWidth);
  196. drawXAxis(vg, strokeWidth);
  197. if (_module->_channelA) {
  198. drawGraph(vg, _module->_channelA->getBins(), _module->_channelA->_binsN, _channelAColor, strokeWidth);
  199. }
  200. if (_module->_channelB) {
  201. drawGraph(vg, _module->_channelB->getBins(), _module->_channelB->_binsN, _channelBColor, strokeWidth);
  202. }
  203. if (_module->_channelC) {
  204. drawGraph(vg, _module->_channelC->getBins(), _module->_channelC->_binsN, _channelCColor, strokeWidth);
  205. }
  206. if (_module->_channelD) {
  207. drawGraph(vg, _module->_channelD->getBins(), _module->_channelD->_binsN, _channelDColor, strokeWidth);
  208. }
  209. nvgRestore(vg);
  210. }
  211. }
  212. void AnalyzerDisplay::drawBackground(NVGcontext* vg) {
  213. nvgSave(vg);
  214. nvgBeginPath(vg);
  215. nvgRect(vg, 0, 0, _size.x, _size.y);
  216. nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
  217. nvgFill(vg);
  218. nvgStrokeColor(vg, nvgRGBA(0xc0, 0xc0, 0xc0, 0xff));
  219. nvgStroke(vg);
  220. nvgRestore(vg);
  221. }
  222. void AnalyzerDisplay::drawHeader(NVGcontext* vg) {
  223. nvgSave(vg);
  224. const int textY = -4;
  225. const int charPx = 5;
  226. const int sLen = 100;
  227. char s[sLen];
  228. int x = _insetAround + 2;
  229. int n = snprintf(s, sLen, "Peaks (+/-%0.1f):", (engineGetSampleRate() / 2.0f) / (float)(_module->size() / _module->_binAverageN));
  230. drawText(vg, s, x, _insetTop + textY);
  231. x += n * charPx - 0;
  232. if (_module->_channelA) {
  233. snprintf(s, sLen, "A:%7.1f", _module->_channelA->getPeak());
  234. drawText(vg, s, x, _insetTop + textY, 0.0, &_channelAColor);
  235. }
  236. x += 9 * charPx + 3;
  237. if (_module->_channelB) {
  238. snprintf(s, sLen, "B:%7.1f", _module->_channelB->getPeak());
  239. drawText(vg, s, x, _insetTop + textY, 0.0, &_channelBColor);
  240. }
  241. x += 9 * charPx + 3;
  242. if (_module->_channelC) {
  243. snprintf(s, sLen, "C:%7.1f", _module->_channelC->getPeak());
  244. drawText(vg, s, x, _insetTop + textY, 0.0, &_channelCColor);
  245. }
  246. x += 9 * charPx + 3;
  247. if (_module->_channelD) {
  248. snprintf(s, sLen, "D:%7.1f", _module->_channelD->getPeak());
  249. drawText(vg, s, x, _insetTop + textY, 0.0, &_channelDColor);
  250. }
  251. nvgRestore(vg);
  252. }
  253. void AnalyzerDisplay::drawYAxis(NVGcontext* vg, float strokeWidth) {
  254. nvgSave(vg);
  255. nvgStrokeColor(vg, _axisColor);
  256. nvgStrokeWidth(vg, strokeWidth);
  257. const int lineX = _insetLeft - 2;
  258. const int textX = 9;
  259. const float textR = -M_PI/2.0;
  260. nvgBeginPath(vg);
  261. int lineY = _insetTop;
  262. nvgMoveTo(vg, lineX, lineY);
  263. nvgLineTo(vg, _size.x - _insetRight, lineY);
  264. nvgStroke(vg);
  265. nvgBeginPath(vg);
  266. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB + 12.0)/_displayDB);
  267. nvgMoveTo(vg, lineX, lineY);
  268. nvgLineTo(vg, _size.x - _insetRight, lineY);
  269. nvgStroke(vg);
  270. drawText(vg, "12", textX, lineY + 5.0, textR);
  271. nvgBeginPath(vg);
  272. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB)/_displayDB);
  273. nvgMoveTo(vg, lineX, lineY);
  274. nvgLineTo(vg, _size.x - _insetRight, lineY);
  275. nvgStrokeWidth(vg, strokeWidth * 1.5);
  276. nvgStroke(vg);
  277. nvgStrokeWidth(vg, strokeWidth);
  278. drawText(vg, "0", textX, lineY + 2.3, textR);
  279. nvgBeginPath(vg);
  280. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB - 12.0)/_displayDB);
  281. nvgMoveTo(vg, lineX, lineY);
  282. nvgLineTo(vg, _size.x - _insetRight, lineY);
  283. nvgStroke(vg);
  284. drawText(vg, "-12", textX, lineY + 10, textR);
  285. nvgBeginPath(vg);
  286. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB - 24.0)/_displayDB);
  287. nvgMoveTo(vg, lineX, lineY);
  288. nvgLineTo(vg, _size.x - _insetRight, lineY);
  289. nvgStroke(vg);
  290. drawText(vg, "-24", textX, lineY + 10, textR);
  291. nvgBeginPath(vg);
  292. lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - _positiveDisplayDB - 48.0)/_displayDB);
  293. nvgMoveTo(vg, lineX, lineY);
  294. nvgLineTo(vg, _size.x - _insetRight, lineY);
  295. nvgStroke(vg);
  296. drawText(vg, "-48", textX, lineY + 10, textR);
  297. nvgBeginPath(vg);
  298. lineY = _insetTop + _graphSize.y + 1;
  299. nvgMoveTo(vg, lineX, lineY);
  300. nvgLineTo(vg, _size.x - _insetRight, lineY);
  301. nvgStroke(vg);
  302. nvgBeginPath(vg);
  303. nvgMoveTo(vg, lineX, _insetTop);
  304. nvgLineTo(vg, lineX, lineY);
  305. nvgStroke(vg);
  306. drawText(vg, "dB", textX, _size.y - _insetBottom, textR);
  307. nvgRestore(vg);
  308. }
  309. void AnalyzerDisplay::drawXAxis(NVGcontext* vg, float strokeWidth) {
  310. nvgSave(vg);
  311. nvgStrokeColor(vg, _axisColor);
  312. nvgStrokeWidth(vg, strokeWidth);
  313. const float maxHz = _module->_range * (engineGetSampleRate() / 2.0);
  314. float hz = 100.0;
  315. while (hz < maxHz && hz < 1001.0) {
  316. drawXAxisLine(vg, hz, maxHz);
  317. hz += 100.0;
  318. }
  319. hz = 2000.0;
  320. while (hz < maxHz && hz < 10001.0) {
  321. drawXAxisLine(vg, hz, maxHz);
  322. hz += 1000.0;
  323. }
  324. hz = 20000.0;
  325. while (hz < maxHz && hz < 100001.0) {
  326. drawXAxisLine(vg, hz, maxHz);
  327. hz += 10000.0;
  328. }
  329. drawText(vg, "Hz", _insetLeft, _size.y - 2);
  330. {
  331. float x = 100.0 / maxHz;
  332. x = powf(x, xAxisLogFactor);
  333. if (x < 1.0) {
  334. x *= _graphSize.x;
  335. drawText(vg, "100", _insetLeft + x - 8, _size.y - 2);
  336. }
  337. }
  338. {
  339. float x = 1000.0 / maxHz;
  340. x = powf(x, xAxisLogFactor);
  341. if (x < 1.0) {
  342. x *= _graphSize.x;
  343. drawText(vg, "1k", _insetLeft + x - 4, _size.y - 2);
  344. }
  345. }
  346. {
  347. float x = 10000.0 / maxHz;
  348. x = powf(x, xAxisLogFactor);
  349. if (x < 1.0) {
  350. x *= _graphSize.x;
  351. drawText(vg, "10k", _insetLeft + x - 7, _size.y - 2);
  352. }
  353. }
  354. nvgRestore(vg);
  355. }
  356. void AnalyzerDisplay::drawXAxisLine(NVGcontext* vg, float hz, float maxHz) {
  357. float x = hz / maxHz;
  358. x = powf(x, xAxisLogFactor);
  359. if (x < 1.0) {
  360. x *= _graphSize.x;
  361. nvgBeginPath(vg);
  362. nvgMoveTo(vg, _insetLeft + x, _insetTop);
  363. nvgLineTo(vg, _insetLeft + x, _insetTop + _graphSize.y);
  364. nvgStroke(vg);
  365. }
  366. }
  367. void AnalyzerDisplay::drawGraph(NVGcontext* vg, const float* bins, int binsN, NVGcolor color, float strokeWidth) {
  368. const int pointsN = roundf(_module->_range*(_module->size()/2));
  369. nvgSave(vg);
  370. nvgScissor(vg, _insetLeft, _insetTop, _graphSize.x, _graphSize.y);
  371. nvgStrokeColor(vg, color);
  372. nvgStrokeWidth(vg, strokeWidth);
  373. nvgBeginPath(vg);
  374. for (int i = 0; i < pointsN; ++i) {
  375. int height = binValueToHeight(bins[i]);
  376. if (i == 0) {
  377. nvgMoveTo(vg, _insetLeft, _insetTop + (_graphSize.y - height));
  378. }
  379. else {
  380. float x = _graphSize.x * powf(i / (float)pointsN, xAxisLogFactor);
  381. nvgLineTo(vg, _insetLeft + x, _insetTop + (_graphSize.y - height));
  382. }
  383. }
  384. nvgStroke(vg);
  385. nvgRestore(vg);
  386. }
  387. void AnalyzerDisplay::drawText(NVGcontext* vg, const char* s, float x, float y, float rotation, const NVGcolor* color) {
  388. nvgSave(vg);
  389. nvgTranslate(vg, x, y);
  390. nvgRotate(vg, rotation);
  391. nvgFontSize(vg, 10);
  392. nvgFontFaceId(vg, _font->handle);
  393. nvgFillColor(vg, color ? *color : _textColor);
  394. nvgText(vg, 0, 0, s, NULL);
  395. nvgRestore(vg);
  396. }
  397. int AnalyzerDisplay::binValueToHeight(float value) {
  398. const float minDB = -(_displayDB - _positiveDisplayDB);
  399. if (value < 0.00001f) {
  400. return 0;
  401. }
  402. 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.
  403. value = powf(value, 0.5f); // undoing magnitude scaling of levels back from the FFT?
  404. value = amplitudeToDecibels(value);
  405. value = std::max(minDB, value);
  406. value = std::min(_positiveDisplayDB, value);
  407. value -= minDB;
  408. value /= _displayDB;
  409. return roundf(_graphSize.y * value);
  410. }
  411. struct OneTenKnob : Knob38 {
  412. OneTenKnob() : Knob38() {
  413. minAngle = -0.664*M_PI;
  414. }
  415. };
  416. struct AnalyzerWidget : ModuleWidget {
  417. static constexpr int hp = 20;
  418. AnalyzerWidget(Analyzer* module) : ModuleWidget(module) {
  419. box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT);
  420. {
  421. auto panel = new SVGPanel();
  422. panel->box.size = box.size;
  423. panel->setBackground(SVG::load(assetPlugin(plugin, "res/Analyzer.svg")));
  424. addChild(panel);
  425. }
  426. {
  427. auto inset = Vec(10, 25);
  428. auto size = Vec(box.size.x - 2*inset.x, 230);
  429. auto display = new AnalyzerDisplay(module, size);
  430. display->box.pos = inset;
  431. display->box.size = size;
  432. addChild(display);
  433. }
  434. addChild(Widget::create<ScrewSilver>(Vec(15, 0)));
  435. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0)));
  436. addChild(Widget::create<ScrewSilver>(Vec(15, 365)));
  437. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365)));
  438. // generated by svg_widgets.rb
  439. auto rangeParamPosition = Vec(35.08, 271.08);
  440. auto smoothParamPosition = Vec(109.08, 271.08);
  441. auto qualityParamPosition = Vec(188.02, 300.02);
  442. auto powerParamPosition = Vec(261.02, 300.02);
  443. auto signalaInputPosition = Vec(13.5, 323.0);
  444. auto signalbInputPosition = Vec(86.5, 323.0);
  445. auto signalcInputPosition = Vec(160.5, 323.0);
  446. auto signaldInputPosition = Vec(233.5, 323.0);
  447. auto signalaOutputPosition = Vec(42.5, 323.0);
  448. auto signalbOutputPosition = Vec(115.5, 323.0);
  449. auto signalcOutputPosition = Vec(189.5, 323.0);
  450. auto signaldOutputPosition = Vec(262.5, 323.0);
  451. auto qualityHighLightPosition = Vec(179.0, 273.0);
  452. auto qualityGoodLightPosition = Vec(179.0, 288.0);
  453. auto powerOnLightPosition = Vec(252.0, 288.0);
  454. // end generated by svg_widgets.rb
  455. addParam(ParamWidget::create<OneTenKnob>(rangeParamPosition, module, Analyzer::RANGE_PARAM, 0.1, 1.0, 0.5));
  456. addParam(ParamWidget::create<Knob38>(smoothParamPosition, module, Analyzer::SMOOTH_PARAM, 0.0, 1.0, 0.5));
  457. addParam(ParamWidget::create<StatefulButton9>(qualityParamPosition, module, Analyzer::QUALITY_PARAM, 1.0, 2.0, 1.0));
  458. {
  459. auto w = ParamWidget::create<StatefulButton9>(powerParamPosition, module, Analyzer::POWER_PARAM, 0.0, 1.0, 1.0);
  460. w->randomizable = false;
  461. addParam(w);
  462. }
  463. addInput(Port::create<Port24>(signalaInputPosition, Port::INPUT, module, Analyzer::SIGNALA_INPUT));
  464. addInput(Port::create<Port24>(signalbInputPosition, Port::INPUT, module, Analyzer::SIGNALB_INPUT));
  465. addInput(Port::create<Port24>(signalcInputPosition, Port::INPUT, module, Analyzer::SIGNALC_INPUT));
  466. addInput(Port::create<Port24>(signaldInputPosition, Port::INPUT, module, Analyzer::SIGNALD_INPUT));
  467. addOutput(Port::create<Port24>(signalaOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALA_OUTPUT));
  468. addOutput(Port::create<Port24>(signalbOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALB_OUTPUT));
  469. addOutput(Port::create<Port24>(signalcOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALC_OUTPUT));
  470. addOutput(Port::create<Port24>(signaldOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALD_OUTPUT));
  471. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(qualityHighLightPosition, module, Analyzer::QUALITY_HIGH_LIGHT));
  472. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(qualityGoodLightPosition, module, Analyzer::QUALITY_GOOD_LIGHT));
  473. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(powerOnLightPosition, module, Analyzer::POWER_ON_LIGHT));
  474. }
  475. };
  476. RACK_PLUGIN_MODEL_INIT(Bogaudio, Analyzer) {
  477. Model *modelAnalyzer = createModel<Analyzer, AnalyzerWidget>("Bogaudio-Analyzer", "Analyzer", "spectrum analyzer", VISUAL_TAG);
  478. return modelAnalyzer;
  479. }